From d5dfe7da45f6dbff79d7e7aecf9d676cd660b99a Mon Sep 17 00:00:00 2001 From: Christopher Pruijsen Date: Tue, 3 Nov 2026 10:26:35 +0000 Subject: [PATCH] [core] Keep the defaultToEmptyContainer default off schemas shared between properties --- .../openapitools/codegen/DefaultCodegen.java | 11 ++- .../codegen/java/JavaClientCodegenTest.java | 26 +++++++ .../test/resources/3_0/java/issue_25522.yaml | 39 ++++++++++ .../java/okhttp-gson/api/openapi.yaml | 78 ++----------------- .../client/model/RequiredNullableBody.java | 2 +- 5 files changed, 82 insertions(+), 74 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/3_0/java/issue_25522.yaml diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 4f52010d72b1..e77f8389a5fb 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -4383,7 +4383,9 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo } // override defaultValue if it's not set and defaultToEmptyContainer is set - if (p.getDefault() == null && defaultToEmptyContainer) { + boolean emptyContainerDefault = p.getDefault() == null && defaultToEmptyContainer; + boolean defaultSetFlag = p.getDefaultSetFlag(); + if (emptyContainerDefault) { updateDefaultToEmptyContainer(property, p); } @@ -4391,6 +4393,13 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo property.defaultValue = toDefaultValue(property, p); property.defaultValueWithParam = toDefaultValueWithParam(name, p); + if (emptyContainerDefault) { + // p may be shared with other properties (e.g. via $ref or allOf) and the empty-container + // default depends on this property's required flag, so take it off the schema again + p.setDefault(null); + p.setDefaultSetFlag(defaultSetFlag); + } + LOGGER.debug("debugging from property return: {}", property); schemaCodegenPropertyCache.put(ns, property); return property; diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java index 256c2203428e..8f369162562a 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java @@ -5893,6 +5893,32 @@ public void testMicroprofileNullableRequiredContainers_issue24776() { .doesNotContain(" = ;"); } + @Test + public void testDefaultToEmptyContainerDoesNotLeakAcrossSharedSchemaRefs_issue25522() { + final Path output = newTempFolder(); + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName(JAVA_GENERATOR) + .setLibrary(JavaClientCodegen.RESTTEMPLATE) + .setAdditionalProperties(Map.of( + CodegenConstants.MODEL_PACKAGE, "org.openapitools.client.model", + "defaultToEmptyContainer", "array|map" + )) + .setInputSpec("src/test/resources/3_0/java/issue_25522.yaml") + .setOutputDir(output.toString().replace("\\", "/")); + + List files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate(); + + validateJavaSourceFiles(files); + // requiredStrings and requiredMap come first and share their schema with the optional ones + assertThat(output.resolve("src/main/java/org/openapitools/client/model/ExampleObject.java")).content() + .contains("private List requiredStrings = new ArrayList<>();") + .contains("private List optionalStrings;") + .contains("private Map requiredMap = new HashMap<>();") + .contains("private Map optionalMap;"); + // the spec declares no default, so none may leak into the exported copy + assertThat(output.resolve("api/openapi.yaml")).content().doesNotContain("default:"); + } + private static JavaClientCodegen newRetrofit2Codegen(Map properties) { JavaClientCodegen codegen = new JavaClientCodegen(); codegen.setLibrary(JavaClientCodegen.RETROFIT_2); diff --git a/modules/openapi-generator/src/test/resources/3_0/java/issue_25522.yaml b/modules/openapi-generator/src/test/resources/3_0/java/issue_25522.yaml new file mode 100644 index 000000000000..03771c694ded --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/java/issue_25522.yaml @@ -0,0 +1,39 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: Example API +paths: + /info: + get: + operationId: getInfo + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ExampleObject' +components: + schemas: + StringArray: + type: array + items: + type: string + StringMap: + type: object + additionalProperties: + type: string + ExampleObject: + type: object + properties: + requiredStrings: + $ref: '#/components/schemas/StringArray' + optionalStrings: + $ref: '#/components/schemas/StringArray' + requiredMap: + $ref: '#/components/schemas/StringMap' + optionalMap: + $ref: '#/components/schemas/StringMap' + required: + - requiredStrings + - requiredMap diff --git a/samples/client/petstore/java/okhttp-gson/api/openapi.yaml b/samples/client/petstore/java/okhttp-gson/api/openapi.yaml index 55552d3bd480..bea8188757eb 100644 --- a/samples/client/petstore/java/okhttp-gson/api/openapi.yaml +++ b/samples/client/petstore/java/okhttp-gson/api/openapi.yaml @@ -108,7 +108,6 @@ paths: name: status required: true schema: - default: [] items: default: available enum: @@ -123,13 +122,11 @@ paths: content: application/xml: schema: - default: [] items: $ref: "#/components/schemas/Pet" type: array application/json: schema: - default: [] items: $ref: "#/components/schemas/Pet" type: array @@ -159,7 +156,6 @@ paths: name: tags required: true schema: - default: [] items: type: string type: array @@ -169,13 +165,11 @@ paths: content: application/xml: schema: - default: [] items: $ref: "#/components/schemas/Pet" type: array application/json: schema: - default: [] items: $ref: "#/components/schemas/Pet" type: array @@ -348,7 +342,6 @@ paths: additionalProperties: format: int32 type: integer - default: [] type: object description: successful operation security: @@ -746,7 +739,6 @@ paths: name: enum_header_string_array required: false schema: - default: [] items: default: $ enum: @@ -774,7 +766,6 @@ paths: name: enum_query_string_array required: false schema: - default: [] items: default: $ enum: @@ -1081,7 +1072,6 @@ paths: schema: additionalProperties: type: string - default: [] type: object description: request body required: true @@ -1223,7 +1213,6 @@ paths: name: pipe required: true schema: - default: [] items: type: string type: array @@ -1233,7 +1222,6 @@ paths: name: ioutil required: true schema: - default: [] items: type: string type: array @@ -1243,7 +1231,6 @@ paths: name: http required: true schema: - default: [] items: type: string type: array @@ -1253,7 +1240,6 @@ paths: name: url required: true schema: - default: [] items: type: string type: array @@ -1263,7 +1249,6 @@ paths: name: context required: true schema: - default: [] items: type: string type: array @@ -1555,7 +1540,6 @@ components: - username: foo - username: bar schema: - default: [] items: $ref: "#/components/schemas/User" type: array @@ -1739,7 +1723,6 @@ components: example: doggie type: string photoUrls: - default: [] items: type: string type: array @@ -1747,7 +1730,6 @@ components: name: photoUrl wrapped: true tags: - default: [] items: $ref: "#/components/schemas/Tag" type: array @@ -1841,7 +1823,6 @@ components: Address: additionalProperties: type: integer - default: [] type: object Animal: discriminator: @@ -1991,15 +1972,12 @@ components: map_property: additionalProperties: type: string - default: [] type: object map_of_map_property: additionalProperties: additionalProperties: type: string - default: [] type: object - default: [] type: object anytype_1: {} map_with_undeclared_properties_anytype_1: @@ -2009,7 +1987,6 @@ components: type: object map_with_undeclared_properties_anytype_3: additionalProperties: true - default: [] type: object empty_map: additionalProperties: false @@ -2019,7 +1996,6 @@ components: map_with_undeclared_properties_string: additionalProperties: type: string - default: [] type: object type: object MixedPropertiesAndAdditionalPropertiesClass: @@ -2033,7 +2009,6 @@ components: map: additionalProperties: $ref: "#/components/schemas/Animal" - default: [] type: object type: object List: @@ -2088,9 +2063,7 @@ components: additionalProperties: additionalProperties: type: string - default: [] type: object - default: [] type: object map_of_enum_string: additionalProperties: @@ -2098,39 +2071,31 @@ components: - UPPER - lower type: string - default: [] type: object direct_map: additionalProperties: type: boolean - default: [] type: object indirect_map: additionalProperties: type: boolean - default: [] type: object type: object ArrayTest: properties: array_of_string: - default: [] items: type: string type: array array_array_of_integer: - default: [] items: - default: [] items: format: int64 type: integer type: array type: array array_array_of_model: - default: [] items: - default: [] items: $ref: "#/components/schemas/ReadOnlyFirst" type: array @@ -2144,7 +2109,6 @@ components: ArrayOfNumberOnly: properties: ArrayNumber: - default: [] items: type: number type: array @@ -2152,9 +2116,7 @@ components: ArrayOfArrayOfNumberOnly: properties: ArrayArrayNumber: - default: [] items: - default: [] items: type: number type: array @@ -2168,7 +2130,6 @@ components: - $ type: string array_enum: - default: [] items: enum: - fish @@ -2178,13 +2139,11 @@ components: type: object FreeFormObject: additionalProperties: true - default: [] description: A schema consisting only of additional properties type: object MapOfString: additionalProperties: type: string - default: [] description: A schema consisting only of additional properties of type string type: object OuterEnum: @@ -2241,7 +2200,6 @@ components: StringBooleanMap: additionalProperties: type: boolean - default: [] type: object FileSchemaTestClass: example: @@ -2254,7 +2212,6 @@ components: file: $ref: "#/components/schemas/File" files: - default: [] items: $ref: "#/components/schemas/File" type: array @@ -2334,7 +2291,6 @@ components: nullable: true type: array array_items_nullable: - default: [] items: nullable: true type: object @@ -2354,7 +2310,6 @@ components: additionalProperties: nullable: true type: object - default: [] type: object type: object fruit: @@ -2484,7 +2439,6 @@ components: nullableShape: $ref: "#/components/schemas/NullableShape" shapes: - default: [] items: $ref: "#/components/schemas/Shape" type: array @@ -2580,7 +2534,6 @@ components: - $ref: "#/components/schemas/GrandparentAnimal" type: object ArrayOfEnums: - default: [] items: $ref: "#/components/schemas/OuterEnum" type: array @@ -2605,7 +2558,6 @@ components: deprecatedRef: $ref: "#/components/schemas/DeprecatedObject" bars: - default: [] deprecated: true items: $ref: "#/components/schemas/Bar" @@ -2623,7 +2575,6 @@ components: example: doggie type: string photoUrls: - default: [] items: type: string type: array @@ -2631,7 +2582,6 @@ components: name: photoUrl wrapped: true tags: - default: [] items: $ref: "#/components/schemas/Tag" type: array @@ -2661,7 +2611,6 @@ components: example: doggie type: string array_allof_dog_property: - default: [] items: $ref: "#/components/schemas/ArrayOfInlineAllOf_array_allof_dog_property_inner" type: array @@ -2690,7 +2639,6 @@ components: type: string type: array WithoutDefault: - default: [] items: type: string type: array @@ -2731,7 +2679,6 @@ components: - type: boolean description: Values of scalar type using anyOf Array: - default: [] description: Values of array type items: $ref: "#/components/schemas/Scalar" @@ -2763,7 +2710,6 @@ components: example: doggie type: string photoUrls: - default: [] items: type: string type: array @@ -2771,7 +2717,6 @@ components: name: photoUrl wrapped: true tags: - default: [] items: allOf: - $ref: "#/components/schemas/Tag" @@ -2832,7 +2777,6 @@ components: example: doggie type: string photoUrls: - default: [] items: type: string type: array @@ -2840,7 +2784,6 @@ components: name: photoUrl wrapped: true tags: - default: [] items: allOf: - $ref: "#/components/schemas/Tag" @@ -2885,15 +2828,13 @@ components: ArrayOneOf: oneOf: - type: integer - - default: [] - items: + - items: type: string type: array ArrayAnyOf: anyOf: - type: integer - - default: [] - items: + - items: type: string type: array ModelWithOneOfAnyOfProperties: @@ -3098,7 +3039,6 @@ components: testEnumParameters_request: properties: enum_form_string_array: - default: [] description: Form parameter enum test (string array) items: default: $ @@ -3204,22 +3144,18 @@ components: type: object _fake_oneOfWIthSameErasure_get_200_response: oneOf: - - default: [] - items: + - items: type: string type: array - - default: [] - items: + - items: type: integer type: array _fake_anyOfWIthSameErasure_get_200_response: anyOf: - - default: [] - items: + - items: type: string type: array - - default: [] - items: + - items: type: integer type: array testInlineFreeformAdditionalProperties_request: @@ -3248,7 +3184,6 @@ components: oneOf: - type: string - additionalProperties: true - default: [] type: object ArrayOfInlineAllOf_array_allof_dog_property_inner: allOf: @@ -3289,7 +3224,6 @@ components: AllOfModelArrayAnyOf_allOf_linkListColumn1: properties: value: - default: [] items: $ref: "#/components/schemas/AllOfModelArrayAnyOf_allOf_linkListColumn1_value" type: array diff --git a/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/RequiredNullableBody.java b/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/RequiredNullableBody.java index 80cf9d3457f5..584bd81e2e15 100644 --- a/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/RequiredNullableBody.java +++ b/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/RequiredNullableBody.java @@ -117,7 +117,7 @@ public class RequiredNullableBody { public static final String SERIALIZED_NAME_OBJECT_ITEMS_NULLABLE = "object_items_nullable"; @SerializedName(SERIALIZED_NAME_OBJECT_ITEMS_NULLABLE) @javax.annotation.Nonnull - private Map objectItemsNullable = new HashMap<>(); + private Map objectItemsNullable; public static final String SERIALIZED_NAME_CUSTOM_REF_ENUM = "custom_ref_enum"; @SerializedName(SERIALIZED_NAME_CUSTOM_REF_ENUM)