Repository: victools/jsonschema-generator (java)

Issue:
Maven plugin with Jackson and ACCEPT_SINGLE_VALUE_AS_ARRAY
Hi,

I am using the `jsonschema-maven-plugin` to generate a JSON Schema for Jackson annotated classes. On the ObjectMapper in the code I set the property `com.fasterxml.jackson.databind.DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY` to `true`. I cannot find a configuration option for the Jackson module of the maven plugin. Is there an option I missed, or is it not (yet) possible to configure such feature on the maven plugin?

Thanks for the work on this projekt, and best regards,
Max

Pull request description:
feat: Introduce Option.ACCEPT_SINGLE_VALUE_AS_ARRAY
Adding new `Option.ACCEPT_SINGLE_VALUE_AS_ARRAY` to support the behavior of Jackson's ` DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY` in the generated schema.

Resolves #458.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 48cc288..1252f19 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 ### `jsonschema-generator`
+#### Added
+- new `Option.ACCEPT_SINGLE_VALUE_AS_ARRAY` to support Jackson `DeserializationFeature` of the same name, i.e., when an array type is declared, an instance of a single item should also be accepted by the schema
+
 #### Changed
 - consider `Boolean` values as valid in `const`/`enum` (i.e., no longer ignore them)
 
diff --git a/jsonschema-examples/src/main/java/com/github/victools/jsonschema/examples/SingleArrayItemExample.java b/jsonschema-examples/src/main/java/com/github/victools/jsonschema/examples/SingleArrayItemExample.java
index 55c7a31..52befe5 100644
--- a/jsonschema-examples/src/main/java/com/github/victools/jsonschema/examples/SingleArrayItemExample.java
+++ b/jsonschema-examples/src/main/java/com/github/victools/jsonschema/examples/SingleArrayItemExample.java
@@ -19,6 +19,7 @@ package com.github.victools.jsonschema.examples;
 import com.fasterxml.classmate.ResolvedType;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.github.victools.jsonschema.generator.MemberScope;
+import com.github.victools.jsonschema.generator.Option;
 import com.github.victools.jsonschema.generator.OptionPreset;
 import com.github.victools.jsonschema.generator.SchemaGenerationContext;
 import com.github.victools.jsonschema.generator.SchemaGenerator;
@@ -42,22 +43,13 @@ public class SingleArrayItemExample implements SchemaGenerationExampleInterface
     @Override
     public ObjectNode generateSchema() {
         SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON);
-        configBuilder.forFields()
-                .withTargetTypeOverridesResolver(this::acceptSingleValueAsArray);
+        configBuilder.with(Option.ACCEPT_SINGLE_VALUE_AS_ARRAY);
         SchemaGeneratorConfig config = configBuilder.build();
         SchemaGenerator generator = new SchemaGenerator(config);
         return generator.generateSchema(Example.class);
     }
 
-    private List<ResolvedType> acceptSingleValueAsArray(MemberScope<?, ?> scope) {
-        if (scope.isContainerType() && !scope.isFakeContainerItemScope()) {
-            return Arrays.asList(scope.getContainerItemType(), scope.getType());
-        }
-        return null;
-    }
-
     static class Example {
-        @NotNull
         public List<ArrayItem> someArray;
     }
 
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 cef08de..096c59c 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
@@ -26,6 +26,7 @@ import com.github.victools.jsonschema.generator.impl.module.InlineSchemaModule;
 import com.github.victools.jsonschema.generator.impl.module.MethodExclusionModule;
 import com.github.victools.jsonschema.generator.impl.module.SimpleTypeModule;
 import com.github.victools.jsonschema.generator.impl.module.SimplifiedOptionalModule;
+import com.github.victools.jsonschema.generator.impl.module.SingleValueAsArrayModule;
 import java.util.Collections;
 import java.util.Set;
 import java.util.function.Supplier;
@@ -222,6 +223,14 @@ public enum Option {
      * @since 4.11.0
      */
     MAP_VALUES_AS_ADDITIONAL_PROPERTIES(AdditionalPropertiesModule::forMapValues, null),
+    /**
+     * Whether each property with a container/{@link java.util.Collection Collection} type should also allow for a single collection item to be
+     * provided instead of an array. This corresponds to the Jackson
+     * {@link com.fasterxml.jackson.databind.DeserializationFeature#ACCEPT_SINGLE_VALUE_AS_ARRAY ACCEPT_SINGLE_VALUE_AS_ARRAY} feature.
+     *
+     * @since 4.36.0
+     */
+    ACCEPT_SINGLE_VALUE_AS_ARRAY(SingleValueAsArrayModule::new, null),
     /**
      * Whether allowed values should always be included in an {@link SchemaKeyword#TAG_ENUM "enum"} keyword. If there is exactly one allowed value, it
      * will otherwise be represented by a {@link SchemaKeyword#TAG_CONST "const"} keyword instead.
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/module/SingleValueAsArrayModule.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/module/SingleValueAsArrayModule.java
new file mode 100644
index 0000000..c1d02f9
--- /dev/null
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/module/SingleValueAsArrayModule.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 VicTools.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.github.victools.jsonschema.generator.impl.module;
+
+import com.fasterxml.classmate.ResolvedType;
+import com.github.victools.jsonschema.generator.MemberScope;
+import com.github.victools.jsonschema.generator.Module;
+import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Default module being included if {@code Option.ACCEPT_SINGLE_VALUE_AS_ARRAY} is enabled.
+ *
+ * @since 4.36.0
+ */
+public class SingleValueAsArrayModule implements Module {
+
+    /**
+     * Allow a container's item type as single instance alternative to an array, or return null for non-containers.
+     *
+     * @param scope targeted field/method
+     * @return collection containing both the container's item type and the container type as such, or null
+     */
+    private static List<ResolvedType> acceptSingleValueAsArray(MemberScope<?, ?> scope) {
+        if (scope.isContainerType() && !scope.isFakeContainerItemScope()) {
+            return Arrays.asList(scope.getContainerItemType(), scope.getType());
+        }
+        return null;
+    }
+
+    @Override
+    public void applyToConfigBuilder(SchemaGeneratorConfigBuilder builder) {
+        builder.forFields()
+                .withTargetTypeOverridesResolver(SingleValueAsArrayModule::acceptSingleValueAsArray);
+        builder.forMethods()
+                .withTargetTypeOverridesResolver(SingleValueAsArrayModule::acceptSingleValueAsArray);
+    }
+}
diff --git a/slate-docs/source/includes/_main-generator-options.md b/slate-docs/source/includes/_main-generator-options.md
index 1c44b67..c99bfbe 100644
--- a/slate-docs/source/includes/_main-generator-options.md
+++ b/slate-docs/source/includes/_main-generator-options.md
@@ -260,23 +260,31 @@ configBuilder.without(
     </tr>
     <tr>
       <td rowspan="2" style="text-align: right">27</td>
+      <td colspan="2"><code>Option.ACCEPT_SINGLE_VALUE_AS_ARRAY</code></td>
+    </tr>
+    <tr>
+      <td>Including an <code>anyOf</code> for every field/method declaring a container type. The <code>anyOf</code> then allows for either the array as declared or just a single item type instead.</td>
+      <td>A container type will be represented by an array with the declared item type.</td>
+    </tr>
+    <tr>
+      <td rowspan="2" style="text-align: right">28</td>
       <td colspan="2"><code>Option.ENUM_KEYWORD_FOR_SINGLE_VALUES</code></td>
     </tr>
     <tr>
       <td>Using the <code>enum</code> keyword for allowed values, even if there is only one.</td>
       <td>In case of a single allowed value, use the <code>const</code> keyword instead of <code>enum</code>.</td>
     </tr>
+    <tr><th>#</th><th>Behavior if included</th><th>Behavior if excluded</th></tr>
     <tr>
-      <td rowspan="2" style="text-align: right">28</td>
+      <td rowspan="2" style="text-align: right">29</td>
       <td colspan="2"><code>Option.FORBIDDEN_ADDITIONAL_PROPERTIES_BY_DEFAULT</code></td>
     </tr>
     <tr>
       <td>Setting the <code>additionalProperties</code> attribute in all object schemas to <code>false</code> by default unless some configuration specifically says something else.</td>
       <td>Omitting the <code>additionalProperties</code> attribute in all object schemas by default (thereby allowing any additional properties) unless some configuration specifically says something else.</td>
     </tr>
-    <tr><th>#</th><th>Behavior if included</th><th>Behavior if excluded</th></tr>
     <tr>
-      <td rowspan="2" style="text-align: right">29</td>
+      <td rowspan="2" style="text-align: right">30</td>
       <td colspan="2"><code>Option.DEFINITIONS_FOR_ALL_OBJECTS</code></td>
     </tr>
     <tr>
@@ -284,7 +292,7 @@ configBuilder.without(
       <td>Only include those entries in the <code>$defs</code>/<code>definitions</code> for object types that are referenced more than once and which are not explicitly declared as "inline" via a custom definition.</td>
     </tr>
     <tr>
-      <td rowspan="2" style="text-align: right">30</td>
+      <td rowspan="2" style="text-align: right">31</td>
       <td colspan="2"><code>Option.DEFINITION_FOR_MAIN_SCHEMA</code></td>
     </tr>
     <tr>
@@ -292,24 +300,24 @@ configBuilder.without(
       <td>Define the main/target type "inline".</td>
     </tr>
     <tr>
-      <td rowspan="2" style="text-align: right">31</td>
+      <td rowspan="2" style="text-align: right">32</td>
       <td colspan="2"><code>Option.DEFINITIONS_FOR_MEMBER_SUPERTYPES</code></td>
     </tr>
     <tr>
       <td>For a member (field/method), having a declared type for which subtypes are being detected, include a single definition with any collected member attributes assigned directly. Any subtypes are only being handled as generic types, i.e., outside of the member context. That means, certain relevant annotations may be ignored (e.g. a jackson <code>@JsonTypeInfo</code> override on a single member would not be correctly reflected in the produced schema).</td>
       <td>For a member (field/method), having a declared type for which subtypes are being detected, include a list of definittions: one for each subtype in the given member's context. This allows independently interpreting contextual information (e.g., member annotations) for each subtype.</td>
     </tr>
+    <tr><th>#</th><th>Behavior if included</th><th>Behavior if excluded</th></tr>
     <tr>
-      <td rowspan="2" style="text-align: right">32</td>
+      <td rowspan="2" style="text-align: right">33</td>
       <td colspan="2"><code>Option.INLINE_ALL_SCHEMAS</code></td>
     </tr>
     <tr>
       <td>Do not include any <code>$defs</code>/<code>definitions</code> but rather define all sub-schemas "inline" – however, this results in an exception being thrown if the given type contains any kind of circular reference.</td>
       <td>Depending on whether <code>DEFINITIONS_FOR_ALL_OBJECTS</code> is included or excluded.</td>
     </tr>
-    <tr><th>#</th><th>Behavior if included</th><th>Behavior if excluded</th></tr>
     <tr>
-      <td rowspan="2" style="text-align: right">33</td>
+      <td rowspan="2" style="text-align: right">34</td>
       <td colspan="2"><code>Option.INLINE_NULLABLE_SCHEMAS</code></td>
     </tr>
     <tr>
@@ -317,7 +325,7 @@ configBuilder.without(
       <td>Depending on whether <code>DEFINITIONS_FOR_ALL_OBJECTS</code> is included or excluded.</td>
     </tr>
     <tr>
-      <td rowspan="2" style="text-align: right">34</td>
+      <td rowspan="2" style="text-align: right">35</td>
       <td colspan="2"><code>Option.PLAIN_DEFINITION_KEYS</code></td>
     </tr>
     <tr>
@@ -325,15 +333,16 @@ configBuilder.without(
       <td>Ensure that the keys for any <code>$defs</code>/<code>definitions</code> are URI compatible (as expected by the JSON Schema specification).</td>
     </tr>
     <tr>
-      <td rowspan="2" style="text-align: right">35</td>
+      <td rowspan="2" style="text-align: right">36</td>
       <td colspan="2"><code>Option.ALLOF_CLEANUP_AT_THE_END</code></td>
     </tr>
+    <tr><th>#</th><th>Behavior if included</th><th>Behavior if excluded</th></tr>
     <tr>
       <td>At the very end of the schema generation reduce <code>allOf</code> wrappers where it is possible without overwriting any attributes – this also affects the results from custom definitions.</td>
       <td>Do not attempt to reduce <code>allOf</code> wrappers but preserve them as they were generated regardless of them being necessary or not.</td>
     </tr>
     <tr>
-      <td rowspan="2" style="text-align: right">36</td>
+      <td rowspan="2" style="text-align: right">37</td>
       <td colspan="2"><code>Option.STRICT_TYPE_INFO</code></td>
     </tr>
     <tr>
@@ -350,7 +359,7 @@ Below, you can find the lists of <code>Option</code>s included/excluded in the r
 * "P_J" = <code>PLAIN_JSON</code>
 
 | #  | Standard `Option`                            | F_D | J_O | P_J |
-|----| -------------------------------------------- | -- | --- | --- |
+|----|----------------------------------------------| -- | --- | --- |
 | 1  | `SCHEMA_VERSION_INDICATOR`                   | ⬜️ | ⬜️ | ✅ |
 | 2  | `ADDITIONAL_FIXED_TYPES`                     | ⬜️ | ⬜️ | ✅ |
 | 3  | `STANDARD_FORMATS`                           | ⬜ | ⬜️ | ✅ |
@@ -377,13 +386,14 @@ Below, you can find the lists of <code>Option</code>s included/excluded in the r
 | 24 | `NULLABLE_ARRAY_ITEMS_ALLOWED`               | ⬜️ | ⬜️ | ⬜️ |
 | 25 | `FIELDS_DERIVED_FROM_ARGUMENTFREE_METHODS`   | ⬜️ | ⬜️ | ⬜️ |
 | 26 | `MAP_VALUES_AS_ADDITIONAL_PROPERTIES`        | ⬜️ | ⬜️ | ⬜️ |
-| 27 | `ENUM_KEYWORD_FOR_SINGLE_VALUES`             | ⬜️ | ⬜️ | ⬜️ |
-| 28 | `FORBIDDEN_ADDITIONAL_PROPERTIES_BY_DEFAULT` | ⬜️ | ⬜️ | ⬜️ |
-| 29 | `DEFINITIONS_FOR_ALL_OBJECTS`                | ⬜️ | ⬜️ | ⬜️ |
-| 30 | `DEFINITION_FOR_MAIN_SCHEMA`                 | ⬜️ | ⬜️ | ⬜️ |
-| 31 | `DEFINITIONS_FOR_MEMBER_SUPERTYPES`          | ⬜️ | ⬜️ | ⬜️ |
-| 32 | `INLINE_ALL_SCHEMAS`                         | ⬜️ | ⬜️ | ⬜️ |
-| 33 | `INLINE_NULLABLE_SCHEMAS`                    | ⬜️ | ⬜️ | ⬜️ |
-| 34 | `PLAIN_DEFINITION_KEYS`                      | ⬜️ | ⬜️ | ⬜️ |
-| 35 | `ALLOF_CLEANUP_AT_THE_END`                   | ✅ | ✅ | ✅ |
-| 36 | `STRICT_TYPE_INFO`                           | ⬜️ | ⬜️ | ⬜️ |
+| 27 | `ACCEPT_SINGLE_VALUE_AS_ARRAY`               | ⬜️ | ⬜️ | ⬜️ |
+| 28 | `ENUM_KEYWORD_FOR_SINGLE_VALUES`             | ⬜️ | ⬜️ | ⬜️ |
+| 29 | `FORBIDDEN_ADDITIONAL_PROPERTIES_BY_DEFAULT` | ⬜️ | ⬜️ | ⬜️ |
+| 30 | `DEFINITIONS_FOR_ALL_OBJECTS`                | ⬜️ | ⬜️ | ⬜️ |
+| 31 | `DEFINITION_FOR_MAIN_SCHEMA`                 | ⬜️ | ⬜️ | ⬜️ |
+| 32 | `DEFINITIONS_FOR_MEMBER_SUPERTYPES`          | ⬜️ | ⬜️ | ⬜️ |
+| 33 | `INLINE_ALL_SCHEMAS`                         | ⬜️ | ⬜️ | ⬜️ |
+| 34 | `INLINE_NULLABLE_SCHEMAS`                    | ⬜️ | ⬜️ | ⬜️ |
+| 35 | `PLAIN_DEFINITION_KEYS`                      | ⬜️ | ⬜️ | ⬜️ |
+| 36 | `ALLOF_CLEANUP_AT_THE_END`                   | ✅ | ✅ | ✅ |
+| 37 | `STRICT_TYPE_INFO`                           | ⬜️ | ⬜️ | ⬜️ |


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 d84ca6a..25e9547 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
@@ -117,7 +117,8 @@ public class SchemaGeneratorComplexTypesTest {
                         .withAnchorResolver(scope -> scope.isContainerType() ? null : "#anchor")
                         .withPropertySorter((_prop1, _prop2) -> 0),
                 "for type in general: ");
-        Module methodModule = configBuilder -> populateConfigPart(configBuilder.with(Option.FIELDS_DERIVED_FROM_ARGUMENTFREE_METHODS)
+        Module methodModule = configBuilder -> populateConfigPart(configBuilder.with(Option.FIELDS_DERIVED_FROM_ARGUMENTFREE_METHODS,
+                Option.ACCEPT_SINGLE_VALUE_AS_ARRAY)
                 .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);
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 4f61dbf..4323275 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
@@ -6,10 +6,16 @@
             "const": 5
         },
         "genericArray": {
-            "type": "array",
-            "items": {
-                "type": "string"
-            }
+            "anyOf": [
+                {
+                    "type": "string"
+                }, {
+                    "type": "array",
+                    "items": {
+                        "type": "string"
+                    }
+                }
+            ]
         },
         "genericValue": {
             "type": ["string", "null"],
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 2543047..5aee334 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
@@ -1,5 +1,20 @@
 {
     "definitions": {
+        "LazyStringSupplier": {
+            "type": "object",
+            "properties": {
+                "get()": {
+                    "type": ["string", "null"],
+                    "title": "String",
+                    "description": "looked-up from method: String",
+                    "const": "constant string value",
+                    "minLength": 1,
+                    "maxLength": 256,
+                    "format": "date",
+                    "pattern": "^.{1,256}$"
+                }
+            }
+        },
         "Optional(Integer)": {
             "type": "object",
             "properties": {
@@ -74,22 +89,37 @@
                     ]
                 },
                 "values()": {
-                    "title": "RoundingMode[]",
-                    "description": "looked-up from method: RoundingMode[]",
-                    "minItems": 2,
-                    "maxItems": 100,
-                    "uniqueItems": false,
-                    "type": ["array", "null"],
-                    "items": {
-                        "allOf": [
-                            {
-                                "$ref": "#/definitions/RoundingMode-nullable"
-                            }, {
-                                "title": "RoundingMode",
-                                "description": "looked-up from method: RoundingMode"
+                    "anyOf": [
+                        {
+                            "type": "null"
+                        }, {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/RoundingMode"
+                                }, {
+                                    "title": "RoundingMode",
+                                    "description": "looked-up from method: RoundingMode"
+                                }
+                            ]
+                        }, {
+                            "title": "RoundingMode[]",
+                            "description": "looked-up from method: RoundingMode[]",
+                            "minItems": 2,
+                            "maxItems": 100,
+                            "uniqueItems": false,
+                            "type": "array",
+                            "items": {
+                                "allOf": [
+                                    {
+                                        "$ref": "#/definitions/RoundingMode-nullable"
+                                    }, {
+                                        "title": "RoundingMode",
+                                        "description": "looked-up from method: RoundingMode"
+                                    }
+                                ]
                             }
-                        ]
-                    }
+                        }
+                    ]
                 }
             }
         },
@@ -110,10 +140,16 @@
                     "const": 5
                 },
                 "genericArray": {
-                    "type": "array",
-                    "items": {
-                        "type": "string"
-                    }
+                    "anyOf": [
+                        {
+                            "type": "string"
+                        }, {
+                            "type": "array",
+                            "items": {
+                                "type": "string"
+                            }
+                        }
+                    ]
                 },
                 "genericValue": {
                     "type": ["string", "null"],
@@ -138,14 +174,20 @@
                 "calculateSomething(Number, Number)": false
             }
         },
-        "TestClass2(Long)-nullable": {
-            "type": ["object", "null"],
+        "TestClass2(Long)": {
+            "type": "object",
             "properties": {
                 "genericArray": {
-                    "type": "array",
-                    "items": {
-                        "type": "integer"
-                    }
+                    "anyOf": [
+                        {
+                            "type": "integer"
+                        }, {
+                            "type": "array",
+                            "items": {
+                                "type": "integer"
+                            }
+                        }
+                    ]
                 },
                 "genericValue": {
                     "type": ["integer", "null"],
@@ -161,14 +203,29 @@
                 }
             }
         },
+        "TestClass2(Long)-nullable": {
+            "anyOf": [
+                {
+                    "type": "null"
+                }, {
+                    "$ref": "#/definitions/TestClass2(Long)"
+                }
+            ]
+        },
         "TestClass2(String)": {
             "type": "object",
             "properties": {
                 "genericArray": {
-                    "type": "array",
-                    "items": {
-                        "type": "string"
-                    }
+                    "anyOf": [
+                        {
+                            "type": "string"
+                        }, {
+                            "type": "array",
+                            "items": {
+                                "type": "string"
+                            }
+                        }
+                    ]
                 },
                 "genericValue": {
                     "type": ["string", "null"],
@@ -201,10 +258,16 @@
                     "type": ["object", "null"],
                     "properties": {
                         "genericArray": {
-                            "type": "array",
-                            "items": {
-                                "$ref": "#/definitions/TestClass2(String)"
-                            }
+                            "anyOf": [
+                                {
+                                    "$ref": "#/definitions/TestClass2(String)"
+                                }, {
+                                    "type": "array",
+                                    "items": {
+                                        "$ref": "#/definitions/TestClass2(String)"
+                                    }
+                                }
+                            ]
                         },
                         "genericValue": {
                             "anyOf": [
@@ -235,31 +298,31 @@
                     }
                 },
                 "listOfOptionalS": {
-                    "type": "array",
-                    "items": {
-                        "$ref": "#/definitions/Optional(Integer)"
-                    }
+                    "anyOf": [
+                        {
+                            "$ref": "#/definitions/Optional(Integer)"
+                        }, {
+                            "type": "array",
+                            "items": {
+                                "$ref": "#/definitions/Optional(Integer)"
+                            }
+                        }
+                    ]
                 },
                 "optionalS": {
                     "$ref": "#/definitions/Optional(Integer)"
                 },
                 "setOfStringSupplier": {
-                    "type": "array",
-                    "items": {
-                        "type": "object",
-                        "properties": {
-                            "get()": {
-                                "type": ["string", "null"],
-                                "title": "String",
-                                "description": "looked-up from method: String",
-                                "const": "constant string value",
-                                "minLength": 1,
-                                "maxLength": 256,
-                                "format": "date",
-                                "pattern": "^.{1,256}$"
+                    "anyOf": [
+                        {
+                            "$ref": "#/definitions/LazyStringSupplier"
+                        }, {
+                            "type": "array",
+                            "items": {
+                                "$ref": "#/definitions/LazyStringSupplier"
                             }
                         }
-                    }
+                    ]
                 },
                 "supplierS": {
                     "type": "object",
@@ -290,39 +353,70 @@
             "type": ["object", "null"],
             "properties": {
                 "genericArray": {
-                    "type": "array",
-                    "items": {
-                        "type": "array",
-                        "items": {
-                            "$ref": "#/definitions/TestClass1"
+                    "anyOf": [
+                        {
+                            "type": "array",
+                            "items": {
+                                "$ref": "#/definitions/TestClass1"
+                            }
+                        }, {
+                            "type": "array",
+                            "items": {
+                                "type": "array",
+                                "items": {
+                                    "$ref": "#/definitions/TestClass1"
+                                }
+                            }
                         }
-                    }
+                    ]
                 },
                 "genericValue": {
-                    "title": "TestClass1[]",
-                    "description": "looked-up from method: TestClass1[]",
-                    "minItems": 2,
-                    "maxItems": 100,
-                    "uniqueItems": false,
-                    "type": ["array", "null"],
-                    "items": {
-                        "anyOf": [
-                            {
-                                "type": "null"
-                            }, {
-                                "$ref": "#/definitions/TestClass1"
-                            }
-                        ],
-                        "title": "TestClass1",
-                        "description": "looked-up from method: TestClass1",
-                        "additionalProperties": false,
-                        "patternProperties": {
-                            "^generic.+$": {
-                                "type": "string"
+                    "anyOf": [
+                        {
+                            "type": "null"
+                        }, {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/TestClass1"
+                                }, {
+                                    "title": "TestClass1",
+                                    "description": "looked-up from method: TestClass1",
+                                    "additionalProperties": false,
+                                    "patternProperties": {
+                                        "^generic.+$": {
+                                            "type": "string"
+                                        }
+                                    },
+                                    "type": ["object", "null"]
+                                }
+                            ]
+                        }, {
+                            "title": "TestClass1[]",
+                            "description": "looked-up from method: TestClass1[]",
+                            "minItems": 2,
+                            "maxItems": 100,
+                            "uniqueItems": false,
+                            "type": "array",
+                            "items": {
+                                "anyOf": [
+                                    {
+                                        "type": "null"
+                                    }, {
+                                        "$ref": "#/definitions/TestClass1"
+                                    }
+                                ],
+                                "title": "TestClass1",
+                                "description": "looked-up from method: TestClass1",
+                                "additionalProperties": false,
+                                "patternProperties": {
+                                    "^generic.+$": {
+                                        "type": "string"
+                                    }
+                                },
+                                "type": ["object", "null"]
                             }
-                        },
-                        "type": ["object", "null"]
-                    }
+                        }
+                    ]
                 }
             },
             "title": "TestClass2<TestClass1[]>",
@@ -355,29 +449,51 @@
             ]
         },
         "nestedLongList": {
-            "title": "List<TestClass2<Long>>",
-            "description": "looked-up from method: List<TestClass2<Long>>",
-            "minItems": 2,
-            "maxItems": 100,
-            "uniqueItems": false,
-            "type": ["array", "null"],
-            "items": {
-                "allOf": [
-                    {
-                        "$ref": "#/definitions/TestClass2(Long)-nullable"
-                    }, {
-                        "title": "TestClass2<Long>",
-                        "description": "looked-up from method: TestClass2<Long>",
-                        "additionalProperties": false,
-                        "patternProperties": {
-                            "^generic.+$": {
-                                "type": "integer"
+            "anyOf": [
+                {
+                    "type": "null"
+                }, {
+                    "allOf": [
+                        {
+                            "$ref": "#/definitions/TestClass2(Long)"
+                        }, {
+                            "title": "TestClass2<Long>",
+                            "description": "looked-up from method: TestClass2<Long>",
+                            "additionalProperties": false,
+                            "patternProperties": {
+                                "^generic.+$": {
+                                    "type": "integer"
+                                }
+                            },
+                            "type": ["object", "null"]
+                        }
+                    ]
+                }, {
+                    "title": "List<TestClass2<Long>>",
+                    "description": "looked-up from method: List<TestClass2<Long>>",
+                    "minItems": 2,
+                    "maxItems": 100,
+                    "uniqueItems": false,
+                    "type": "array",
+                    "items": {
+                        "allOf": [
+                            {
+                                "$ref": "#/definitions/TestClass2(Long)-nullable"
+                            }, {
+                                "title": "TestClass2<Long>",
+                                "description": "looked-up from method: TestClass2<Long>",
+                                "additionalProperties": false,
+                                "patternProperties": {
+                                    "^generic.+$": {
+                                        "type": "integer"
+                                    }
+                                },
+                                "type": ["object", "null"]
                             }
-                        },
-                        "type": ["object", "null"]
+                        ]
                     }
-                ]
-            }
+                }
+            ]
         }
     }
 }