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 40482655ea51..25581dc047a6 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 @@ -2814,7 +2814,9 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map newProperties = new LinkedHashMap<>(); addProperties(newProperties, required, refSchema, new HashSet<>()); mergeProperties(properties, newProperties); - addProperties(allProperties, allRequired, refSchema, new HashSet<>()); + Map newAllProperties = new LinkedHashMap<>(); + addProperties(newAllProperties, allRequired, refSchema, new HashSet<>()); + mergeProperties(allProperties, newAllProperties); } } @@ -2896,15 +2898,29 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map existingProperties, Map newProperties) { // https://github.com/OpenAPITools/openapi-generator/issues/12545 if (null != existingProperties && null != newProperties) { - Schema existingType = existingProperties.get("type"); Schema newType = newProperties.get("type"); - newProperties.forEach((key, value) -> - existingProperties.put(key, ModelUtils.cloneSchema(value, specVersionGreaterThanOrEqualTo310(openAPI))) - ); + newProperties.forEach((key, value) -> { + if (!existingProperties.containsKey(key)) { + // Property not seen before: pull it up from this sub-schema. + existingProperties.put(key, ModelUtils.cloneSchema(value, specVersionGreaterThanOrEqualTo310(openAPI))); + } else if (!isSchemasTypeCompatible(existingProperties.get(key), value)) { + // Same property name but incompatible types across sub-schemas: drop it so the + // parent does not end up with an ambiguous field (e.g. coordinates: number[] + // in Polygon vs coordinates: number[][] in MultiPolygon). + existingProperties.remove(key); + } + // Compatible type already present: keep the existing entry unchanged. + }); + // Merge enum values for the 'type' discriminator property if it is still present. + Schema existingType = existingProperties.get("type"); if (null != existingType && null != newType && null != newType.getEnum() && !newType.getEnum().isEmpty()) { for (Object e : newType.getEnum()) { // ensure all interface enum types are added to schema @@ -2917,6 +2933,25 @@ private void mergeProperties(Map existingProperties, Map "coordinates".equals(cp.baseName)); + boolean coordinatesInAllVars = geoJsonObject.allVars.stream() + .anyMatch(cp -> "coordinates".equals(cp.baseName)); + assertFalse(coordinatesInVars); + assertFalse(coordinatesInAllVars); + } + private List getRequiredVars(CodegenModel model) { return getNames(model.getRequiredVars()); } 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 b34fce2ee042..070735dc4c6b 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 @@ -4325,4 +4325,32 @@ public void testJspecify(String library, boolean useSpringBoot4, boolean hasJspe .fileContains("@org.jspecify.annotations.NullMarked"); } + + @Test + public void testGeoJsonObjectDoesNotContainCoordinates() { + // The generated GeoJsonObject class should not have a 'coordinates' field, + // even though both Polygon and MultiPolygon (its oneOf subtypes) define one. + // Polygon has coordinates: List, MultiPolygon has coordinates: List>, + // so the types are incompatible and the field must not be pulled up to the parent. + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/geojson_discriminator.yaml"); + final JavaClientCodegen codegen = new JavaClientCodegen(); + codegen.setOpenAPI(openAPI); + codegen.setOutputDir(newTempFolder().toString()); + + Map files = new DefaultGenerator() + .opts(new ClientOptInput().openAPI(openAPI).config(codegen)) + .generate() + .stream() + .collect(Collectors.toMap(File::getName, Function.identity())); + + // GeoJsonObject must not expose coordinates — the types differ across oneOf variants + JavaFileAssert.assertThat(files.get("GeoJsonObject.java")) + .fileDoesNotContain("coordinates"); + + // Each subtype must still carry its own coordinates field + JavaFileAssert.assertThat(files.get("Polygon.java")) + .assertProperty("coordinates"); + JavaFileAssert.assertThat(files.get("MultiPolygon.java")) + .assertProperty("coordinates"); + } } diff --git a/modules/openapi-generator/src/test/resources/3_0/geojson_discriminator.yaml b/modules/openapi-generator/src/test/resources/3_0/geojson_discriminator.yaml new file mode 100644 index 000000000000..19c77effac8d --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/geojson_discriminator.yaml @@ -0,0 +1,41 @@ +openapi: 3.0.3 +info: + title: GeoJSON Discriminator Test + version: 1.0.0 +paths: {} +components: + schemas: + GeoJsonObject: + type: object + properties: + type: + type: string + required: + - type + discriminator: + propertyName: type + oneOf: + - $ref: '#/components/schemas/Polygon' + - $ref: '#/components/schemas/MultiPolygon' + Polygon: + allOf: + - $ref: '#/components/schemas/GeoJsonObject' + - type: object + properties: + coordinates: + type: array + items: + type: number + format: double + MultiPolygon: + allOf: + - $ref: '#/components/schemas/GeoJsonObject' + - type: object + properties: + coordinates: + type: array + items: + type: array + items: + type: number + format: double