Repository: victools/jsonschema-generator
Pull request #119 (2020-06-26 21:48:12)

feat: new option to derive fields from methods
Introducing `Option.FIELDS_DERIVED_FROM_ARGUMENTFREE_METHODS` to address feature request mentioned in GH-115.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eea9605..7b8d34c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,9 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 #### Added
 - Possibility to configure `SchemaDefinitionNamingStrategy` via `configBuilder.forTypesInGeneral().withDefinitionNamingStrategy()`
 - Explicit `DefaultSchemaDefinitionNamingStrategy` that is being applied if no other `SchemaDefinitionNamingStrategy` is being configured
+- New `Option.FIELDS_DERIVED_FROM_ARGUMENTFREE_METHODS` to allow deriving fields from getter methods
 
 #### Changed
 - Determine names/keys of subschemas in `definitions`/`$defs` through configurable `SchemaDefinitionNamingStrategy`
+- Default property sort order to consider property name when differentiating fields and methods (now that methods may have field-like names)
 
 #### Fixed
 - For multiple definitions for the same type, they might be unnecessarily treated as having conflicting definition names requiring resolution
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/FieldScope.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/FieldScope.java
index 5e44196..c496730 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/FieldScope.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/FieldScope.java
@@ -84,7 +84,7 @@ public class FieldScope extends MemberScope<ResolvedField, Field> {
      * @see #getName()
      */
     @Override
-    public String getSchemaPropertyName() {
+    protected String doGetSchemaPropertyName() {
         return this.getName();
     }
 
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/MemberScope.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/MemberScope.java
index 144e850..43e29cd 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/MemberScope.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/MemberScope.java
@@ -21,6 +21,7 @@ import com.fasterxml.classmate.ResolvedTypeWithMembers;
 import com.fasterxml.classmate.members.ResolvedField;
 import com.fasterxml.classmate.members.ResolvedMember;
 import com.fasterxml.classmate.members.ResolvedMethod;
+import com.github.victools.jsonschema.generator.impl.LazyValue;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Member;
 import java.util.Optional;
@@ -39,6 +40,7 @@ public abstract class MemberScope<M extends ResolvedMember<T>, T extends Member>
     private final ResolvedTypeWithMembers declaringTypeMembers;
     private boolean fakeContainerItemScope;
     private final TypeContext context;
+    private final LazyValue<String> schemaPropertyName = new LazyValue<>(this::doGetSchemaPropertyName);
 
     /**
      * Constructor.
@@ -297,7 +299,16 @@ public abstract class MemberScope<M extends ResolvedMember<T>, T extends Member>
      *
      * @return member's name in parent "properties"
      */
-    public abstract String getSchemaPropertyName();
+    public String getSchemaPropertyName() {
+        return this.schemaPropertyName.get();
+    }
+
+    /**
+     * Returns the name to be used to reference this member in its parent's "properties".
+     *
+     * @return member's name in parent "properties"
+     */
+    protected abstract String doGetSchemaPropertyName();
 
     @Override
     public String toString() {
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/MethodScope.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/MethodScope.java
index 2187106..26930fe 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/MethodScope.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/MethodScope.java
@@ -217,11 +217,22 @@ public class MethodScope extends MemberScope<ResolvedMethod, Method> {
      * @see TypeContext#getMethodPropertyArgumentTypeDescription(ResolvedType)
      */
     @Override
-    public String getSchemaPropertyName() {
+    protected String doGetSchemaPropertyName() {
         String result = this.getName();
-        result += this.getArgumentTypes().stream()
-                .map(this.getContext()::getMethodPropertyArgumentTypeDescription)
-                .collect(Collectors.joining(", ", "(", ")"));
+        if (this.getContext().isDerivingFieldsFromArgumentFreeMethods() && this.getArgumentCount() == 0) {
+            if (this.getOverriddenName() == null) {
+                // remove the "get"/"is" prefix from non-overridden method names
+                if (result.startsWith("get") && result.length() > 3) {
+                    result = Character.toLowerCase(result.charAt(3)) + result.substring(4);
+                } else if (result.startsWith("is") && result.length() > 2) {
+                    result = Character.toLowerCase(result.charAt(2)) + result.substring(3);
+                }
+            }
+        } else {
+            result += this.getArgumentTypes().stream()
+                    .map(this.getContext()::getMethodPropertyArgumentTypeDescription)
+                    .collect(Collectors.joining(", ", "(", ")"));
+        }
         return result;
     }
 }
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/Option.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/Option.java
index d6457cd..5e18966 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/Option.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/Option.java
@@ -195,6 +195,10 @@ public enum Option {
      * Without this option, all methods' return values are deemed not-nullable unless a specific check says otherwise.
      */
     NULLABLE_METHOD_RETURN_VALUES_BY_DEFAULT(null, null),
+    /**
+     * Whether methods that do not expect any arguments, should be treated as fields: i.e. renaming the property.
+     */
+    FIELDS_DERIVED_FROM_ARGUMENTFREE_METHODS(null, null),
     /**
      * Whether a {@link java.util.Map Map&lt;K, V&gt;} schema's "additionalProperties" should be set to its declared value type {@code V}.
      *
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGenerator.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGenerator.java
index 374184b..34dff07 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGenerator.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGenerator.java
@@ -35,7 +35,7 @@ public class SchemaGenerator {
      * @param config configuration to be applied
      */
     public SchemaGenerator(SchemaGeneratorConfig config) {
-        this(config, TypeContextFactory.createDefaultTypeContext());
+        this(config, TypeContextFactory.createDefaultTypeContext(config));
     }
 
     /**
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java
index 028a612..b545c32 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java
@@ -108,6 +108,13 @@ public interface SchemaGeneratorConfig {
      */
     boolean shouldIncludeStaticMethods();
 
+    /**
+     * Determine whether non-void argument-free "getXyZ"/"isXyZ" methods should be represented by the respective field name "xyZ" by default.
+     *
+     * @return whether to treat argument-free methods as fields in schema
+     */
+    boolean shouldDeriveFieldsFromArgumentFreeMethods();
+
     /**
      * Getter for the underlying object mapper.
      *
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java
index 96bca70..c97e391 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java
@@ -36,6 +36,7 @@ public class TypeContext {
     private final TypeResolver typeResolver;
     private final MemberResolver memberResolver;
     private final AnnotationConfiguration annotationConfig;
+    private final boolean derivingFieldsFromArgumentFreeMethods;
 
     /**
      * Constructor.
@@ -43,9 +44,39 @@ public class TypeContext {
      * @param annotationConfig annotation configuration to apply when collecting resolved fields and methods
      */
     public TypeContext(AnnotationConfiguration annotationConfig) {
+        this(annotationConfig, false);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param annotationConfig annotation configuration to apply when collecting resolved fields and methods
+     * @param generatorConfig generator configuration indicating whether argument free methods should be represented as fields
+     */
+    public TypeContext(AnnotationConfiguration annotationConfig, SchemaGeneratorConfig generatorConfig) {
+        this(annotationConfig, generatorConfig.shouldDeriveFieldsFromArgumentFreeMethods());
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param annotationConfig annotation configuration to apply when collecting resolved fields and methods
+     * @param derivingFieldsFromArgumentFreeMethods whether argument free methods should be represented as fields
+     */
+    private TypeContext(AnnotationConfiguration annotationConfig, boolean derivingFieldsFromArgumentFreeMethods) {
         this.typeResolver = new TypeResolver();
         this.memberResolver = new MemberResolver(this.typeResolver);
         this.annotationConfig = annotationConfig;
+        this.derivingFieldsFromArgumentFreeMethods = derivingFieldsFromArgumentFreeMethods;
+    }
+
+    /**
+     * Getter for the flag indicating whether to derive fields from argument-free methods.
+     *
+     * @return whether argument-free methods should be represented as fields
+     */
+    public boolean isDerivingFieldsFromArgumentFreeMethods() {
+        return this.derivingFieldsFromArgumentFreeMethods;
     }
 
     /**
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/PropertySortUtils.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/PropertySortUtils.java
index 374a55d..13c0104 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/PropertySortUtils.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/PropertySortUtils.java
@@ -17,7 +17,6 @@
 package com.github.victools.jsonschema.generator.impl;
 
 import com.github.victools.jsonschema.generator.MemberScope;
-import com.github.victools.jsonschema.generator.MethodScope;
 import java.util.Comparator;
 
 /**
@@ -29,7 +28,7 @@ public class PropertySortUtils {
      * {@link Comparator} sorting properties: with fields before methods.
      */
     public static final Comparator<MemberScope<?, ?>> SORT_PROPERTIES_FIELDS_BEFORE_METHODS
-            = (first, second) -> Boolean.compare(first instanceof MethodScope, second instanceof MethodScope);
+            = (first, second) -> Boolean.compare(first.getSchemaPropertyName().endsWith(")"), second.getSchemaPropertyName().endsWith(")"));
 
     /**
      * {@link Comparator} sorting properties: alphabetically by their name.
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java
index c3ebba0..d2db029 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java
@@ -139,6 +139,11 @@ public class SchemaGeneratorConfigImpl implements SchemaGeneratorConfig {
         return this.isOptionEnabled(Option.STATIC_METHODS);
     }
 
+    @Override
+    public boolean shouldDeriveFieldsFromArgumentFreeMethods() {
+        return this.isOptionEnabled(Option.FIELDS_DERIVED_FROM_ARGUMENTFREE_METHODS);
+    }
+
     @Override
     public boolean shouldIncludeSchemaVersionIndicator() {
         return this.isOptionEnabled(Option.SCHEMA_VERSION_INDICATOR);
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/TypeContextFactory.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/TypeContextFactory.java
index 67bdbdb..0cb9a37 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/TypeContextFactory.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/TypeContextFactory.java
@@ -18,6 +18,7 @@ package com.github.victools.jsonschema.generator.impl;
 
 import com.fasterxml.classmate.AnnotationConfiguration;
 import com.fasterxml.classmate.AnnotationInclusion;
+import com.github.victools.jsonschema.generator.SchemaGeneratorConfig;
 import com.github.victools.jsonschema.generator.TypeContext;
 
 /**
@@ -36,6 +37,17 @@ public class TypeContextFactory {
         return TypeContextFactory.createTypeContext(AnnotationInclusion.INCLUDE_AND_INHERIT_IF_INHERITED);
     }
 
+    /**
+     * Create the default {@link TypeContext} with {@link AnnotationInclusion#INCLUDE_AND_INHERIT_IF_INHERITED}.<br>
+     * This is equivalent to calling: {@code TypeContextFactory.createTypeContext(AnnotationInclusion.INCLUDE_AND_INHERIT_IF_INHERITED)}
+     *
+     * @param config configuration to consider
+     * @return created {@link TypeContext} instance
+     */
+    public static TypeContext createDefaultTypeContext(SchemaGeneratorConfig config) {
+        return TypeContextFactory.createTypeContext(AnnotationInclusion.INCLUDE_AND_INHERIT_IF_INHERITED, config);
+    }
+
     /**
      * Create the a {@link TypeContext} with the given {@link AnnotationInclusion}.
      * <br>
@@ -48,6 +60,19 @@ public class TypeContextFactory {
         return TypeContextFactory.createTypeContext(new AnnotationConfiguration.StdConfiguration(annotationInclusion));
     }
 
+    /**
+     * Create the a {@link TypeContext} with the given {@link AnnotationInclusion}.
+     * <br>
+     * This is equivalent to calling: {@code TypeContextFactory.createTypeContext(new AnnotationConfiguration.StdConfiguration(annotationInclusion))}
+     *
+     * @param annotationInclusion indication which annotations to include during type resolution/introspection
+     * @param config configuration to consider
+     * @return created {@link TypeContext} instance
+     */
+    public static TypeContext createTypeContext(AnnotationInclusion annotationInclusion, SchemaGeneratorConfig config) {
+        return TypeContextFactory.createTypeContext(new AnnotationConfiguration.StdConfiguration(annotationInclusion), config);
+    }
+
     /**
      * Create the a {@link TypeContext} with the given {@link AnnotationConfiguration}.
      *
@@ -57,4 +82,15 @@ public class TypeContextFactory {
     public static TypeContext createTypeContext(AnnotationConfiguration annotationConfig) {
         return new TypeContext(annotationConfig);
     }
+
+    /**
+     * Create the a {@link TypeContext} with the given {@link AnnotationConfiguration}.
+     *
+     * @param annotationConfig configuration determining which annotations to include during type resolution/introspection
+     * @param config configuration to consider
+     * @return created {@link TypeContext} instance
+     */
+    public static TypeContext createTypeContext(AnnotationConfiguration annotationConfig, SchemaGeneratorConfig config) {
+        return new TypeContext(annotationConfig, config);
+    }
 }


Test patch:
diff --git a/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorComplexTypesTest.java b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorComplexTypesTest.java
index 98e72eb..a1bf6c0 100644
--- a/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorComplexTypesTest.java
+++ b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorComplexTypesTest.java
@@ -113,7 +113,8 @@ public class SchemaGeneratorComplexTypesTest {
                         .withAnchorResolver(scope -> scope.isContainerType() ? null : "#anchor")
                         .withPropertySorter((_prop1, _prop2) -> 0),
                 "for type in general: ");
-        Module methodModule = configBuilder -> populateConfigPart(configBuilder.forMethods(), "looked-up from method: ");
+        Module methodModule = configBuilder -> populateConfigPart(configBuilder.with(Option.FIELDS_DERIVED_FROM_ARGUMENTFREE_METHODS)
+                .forMethods(), "looked-up from method: ");
         Module fieldModule = configBuilder -> populateConfigPart(configBuilder.with(Option.INLINE_ALL_SCHEMAS).forFields(), "looked-up from field: ");
         Module enumToStringModule = configBuilder -> configBuilder.with(Option.FLATTENED_ENUMS_FROM_TOSTRING);
         return new Object[][]{
diff --git a/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass1-JAVA_OBJECT-methodattributes.json b/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass1-JAVA_OBJECT-methodattributes.json
index 3bf7601..4f61dbf 100644
--- a/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass1-JAVA_OBJECT-methodattributes.json
+++ b/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass1-JAVA_OBJECT-methodattributes.json
@@ -11,8 +11,7 @@
                 "type": "string"
             }
         },
-        "calculateSomething(Number, Number)": false,
-        "getGenericValue()": {
+        "genericValue": {
             "type": ["string", "null"],
             "title": "String",
             "description": "looked-up from method: String",
@@ -22,15 +21,16 @@
             "format": "date",
             "pattern": "^.{1,256}$"
         },
-        "getPrimitiveValue()": {
+        "primitiveValue": {
             "type": ["integer", "null"],
             "title": "int",
             "description": "looked-up from method: int"
         },
-        "isSimpleTestClass()": {
+        "simpleTestClass": {
             "type": ["boolean", "null"],
             "title": "boolean",
             "description": "looked-up from method: boolean"
-        }
+        },
+        "calculateSomething(Number, Number)": false
     }
 }
diff --git a/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass3-JAVA_OBJECT-methodattributes.json b/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass3-JAVA_OBJECT-methodattributes.json
index e01e728..55fffdc 100644
--- a/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass3-JAVA_OBJECT-methodattributes.json
+++ b/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass3-JAVA_OBJECT-methodattributes.json
@@ -3,12 +3,7 @@
         "RoundingMode": {
             "type": "object",
             "properties": {
-                "compareTo(RoundingMode)": {
-                    "type": ["integer", "null"],
-                    "title": "int",
-                    "description": "looked-up from method: int"
-                },
-                "name()": {
+                "name": {
                     "type": ["string", "null"],
                     "title": "String",
                     "description": "looked-up from method: String",
@@ -18,6 +13,27 @@
                     "format": "date",
                     "pattern": "^.{1,256}$"
                 },
+                "values": {
+                    "title": "RoundingMode[]",
+                    "description": "looked-up from method: RoundingMode[]",
+                    "minItems": 2,
+                    "maxItems": 100,
+                    "uniqueItems": false,
+                    "type": ["array", "null"],
+                    "items": {
+                        "allOf": [{
+                                "$ref": "#/definitions/RoundingMode"
+                            }, {
+                                "title": "RoundingMode",
+                                "description": "looked-up from method: RoundingMode"
+                            }]
+                    }
+                },
+                "compareTo(RoundingMode)": {
+                    "type": ["integer", "null"],
+                    "title": "int",
+                    "description": "looked-up from method: int"
+                },
                 "valueOf(String)": {
                     "allOf": [{
                             "$ref": "#/definitions/RoundingMode-nullable"
@@ -33,22 +49,6 @@
                             "title": "RoundingMode",
                             "description": "looked-up from method: RoundingMode"
                         }]
-                },
-                "values()": {
-                    "title": "RoundingMode[]",
-                    "description": "looked-up from method: RoundingMode[]",
-                    "minItems": 2,
-                    "maxItems": 100,
-                    "uniqueItems": false,
-                    "type": ["array", "null"],
-                    "items": {
-                        "allOf": [{
-                                "$ref": "#/definitions/RoundingMode"
-                            }, {
-                                "title": "RoundingMode",
-                                "description": "looked-up from method: RoundingMode"
-                            }]
-                    }
                 }
             }
         },
@@ -72,8 +72,7 @@
                         "type": "string"
                     }
                 },
-                "calculateSomething(Number, Number)": false,
-                "getGenericValue()": {
+                "genericValue": {
                     "type": ["string", "null"],
                     "title": "String",
                     "description": "looked-up from method: String",
@@ -83,16 +82,17 @@
                     "format": "date",
                     "pattern": "^.{1,256}$"
                 },
-                "getPrimitiveValue()": {
+                "primitiveValue": {
                     "type": ["integer", "null"],
                     "title": "int",
                     "description": "looked-up from method: int"
                 },
-                "isSimpleTestClass()": {
+                "simpleTestClass": {
                     "type": ["boolean", "null"],
                     "title": "boolean",
                     "description": "looked-up from method: boolean"
-                }
+                },
+                "calculateSomething(Number, Number)": false
             }
         },
         "TestClass2(Long)": {
@@ -104,7 +104,7 @@
                         "type": "integer"
                     }
                 },
-                "getGenericValue()": {
+                "genericValue": {
                     "type": ["integer", "null"],
                     "title": "Long",
                     "description": "looked-up from method: Long",
@@ -127,7 +127,7 @@
                         "type": "string"
                     }
                 },
-                "getGenericValue()": {
+                "genericValue": {
                     "type": ["string", "null"],
                     "title": "String",
                     "description": "looked-up from method: String",
@@ -142,7 +142,7 @@
     },
     "type": "object",
     "properties": {
-        "getClass4()": {
+        "class4": {
             "type": ["object", "null"],
             "properties": {
                 "DEFAULT_ROUNDING_MODE": {
@@ -152,41 +152,7 @@
                             "const": "HALF_UP"
                         }]
                 },
-                "optionalS": {
-                    "type": "object",
-                    "properties": {
-                        "get()": {
-                            "type": ["integer", "null"],
-                            "title": "Integer",
-                            "description": "looked-up from method: Integer",
-                            "default": 1,
-                            "enum": [1, 2, 3, 4, 5],
-                            "minimum": 1,
-                            "exclusiveMinimum": 0,
-                            "maximum": 1E+1,
-                            "exclusiveMaximum": 11,
-                            "multipleOf": 1
-                        },
-                        "isPresent()": {
-                            "type": ["boolean", "null"],
-                            "title": "boolean",
-                            "description": "looked-up from method: boolean"
-                        },
-                        "orElse(Integer)": {
-                            "type": ["integer", "null"],
-                            "title": "Integer",
-                            "description": "looked-up from method: Integer",
-                            "default": 1,
-                            "enum": [1, 2, 3, 4, 5],
-                            "minimum": 1,
-                            "exclusiveMinimum": 0,
-                            "maximum": 1E+1,
-                            "exclusiveMaximum": 11,
-                            "multipleOf": 1
-                        }
-                    }
-                },
-                "getClass2OfClass2OfT()": {
+                "class2OfClass2OfT": {
                     "type": ["object", "null"],
                     "properties": {
                         "genericArray": {
@@ -195,7 +161,7 @@
                                 "$ref": "#/definitions/TestClass2(String)"
                             }
                         },
-                        "getGenericValue()": {
+                        "genericValue": {
                             "anyOf": [{
                                     "type": "null"
                                 }, {
@@ -219,6 +185,40 @@
                             "$ref": "#/definitions/TestClass2(String)"
                         }
                     }
+                },
+                "optionalS": {
+                    "type": "object",
+                    "properties": {
+                        "get": {
+                            "type": ["integer", "null"],
+                            "title": "Integer",
+                            "description": "looked-up from method: Integer",
+                            "default": 1,
+                            "enum": [1, 2, 3, 4, 5],
+                            "minimum": 1,
+                            "exclusiveMinimum": 0,
+                            "maximum": 1E+1,
+                            "exclusiveMaximum": 11,
+                            "multipleOf": 1
+                        },
+                        "present": {
+                            "type": ["boolean", "null"],
+                            "title": "boolean",
+                            "description": "looked-up from method: boolean"
+                        },
+                        "orElse(Integer)": {
+                            "type": ["integer", "null"],
+                            "title": "Integer",
+                            "description": "looked-up from method: Integer",
+                            "default": 1,
+                            "enum": [1, 2, 3, 4, 5],
+                            "minimum": 1,
+                            "exclusiveMinimum": 0,
+                            "maximum": 1E+1,
+                            "exclusiveMaximum": 11,
+                            "multipleOf": 1
+                        }
+                    }
                 }
             },
             "title": "TestClass4<Integer, String>",
@@ -227,7 +227,7 @@
                 "type": "string"
             }
         },
-        "getNestedClass1Array()": {
+        "nestedClass1Array": {
             "type": ["object", "null"],
             "properties": {
                 "genericArray": {
@@ -239,7 +239,7 @@
                         }
                     }
                 },
-                "getGenericValue()": {
+                "genericValue": {
                     "title": "TestClass1[]",
                     "description": "looked-up from method: TestClass1[]",
                     "minItems": 2,
@@ -274,7 +274,7 @@
                 }
             }
         },
-        "getNestedLong()": {
+        "nestedLong": {
             "anyOf": [{
                     "type": "null"
                 }, {
@@ -289,7 +289,7 @@
                 }
             }
         },
-        "getNestedLongList()": {
+        "nestedLongList": {
             "title": "List<TestClass2<Long>>",
             "description": "looked-up from method: List<TestClass2<Long>>",
             "minItems": 2,