diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java index f2909042bbba..0473d20ddafc 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java @@ -983,7 +983,24 @@ public Schema normalizeSchema(Schema schema, Set visitedSchemas) { normalizeProperties(schema, visitedSchemas); } else if (schema.getAdditionalProperties() instanceof Schema) { // map normalizeMapSchema(schema); - normalizeSchema((Schema) schema.getAdditionalProperties(), visitedSchemas); + Schema additionalProperties = (Schema) schema.getAdditionalProperties(); + if (getRule(NORMALIZE_31SPEC) && ModelUtils.isNullTypeSchema(openAPI, additionalProperties)) { + // OAS 3.1 allows a map value schema of `type: "null"` (e.g. + // `additionalProperties: { type: "null" }`). There's no OAS 3.0 equivalent type, + // so generators emit a fictional `Null` / `ModelNull` value type that fails to + // compile. Normalize it to an any-type nullable schema so the map value is + // generated as a normal (nullable) object instead. + Schema anyTypeNullable = new Schema(); + anyTypeNullable.setNullable(true); + schema.setAdditionalProperties(anyTypeNullable); + } else { + Schema normalized = normalizeSchema(additionalProperties, visitedSchemas); + if (getRule(NORMALIZE_31SPEC)) { + // capture the normalized value schema (e.g. an OAS 3.1 `type: [array, "null"]` + // value is rewritten to a proper array schema), which would otherwise be lost. + schema.setAdditionalProperties(normalized); + } + } } else if (schema instanceof BooleanSchema) { normalizeBooleanSchema(schema, visitedSchemas); } else if (schema instanceof IntegerSchema) { diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java index 53cacea15650..74d0318280ca 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java @@ -1896,4 +1896,29 @@ public void testLooseNullDefinitions() { ModelUtils.looseNullDefinitions = false; } + @Test + public void testOpenAPINormalizer31SpecNullMapAdditionalProperties() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/issue_23945.yaml"); + Map inputRules = Map.of("NORMALIZE_31SPEC", "true"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, inputRules); + openAPINormalizer.normalize(); + + Schema schema = openAPI.getComponents().getSchemas().get("NullableMaps"); + + // `additionalProperties: { type: "null" }` must not keep the OAS 3.1 `null` type (which makes + // generators emit a fictional `Null` / `ModelNull` value type); it is normalized to an + // any-type nullable schema so the map value generates as a normal (nullable) object. + Schema stringMapValue = ModelUtils.getAdditionalProperties((Schema) schema.getProperties().get("stringMap")); + assertNull(stringMapValue.getType()); + assertNull(stringMapValue.getTypes()); + assertTrue(stringMapValue.getNullable()); + + // `additionalProperties: { type: [array, "null"], items: ... }` is normalized to a proper + // (nullable) array value schema rather than being left half-converted. + Schema errorsValue = ModelUtils.getAdditionalProperties((Schema) schema.getProperties().get("errorsByKey")); + assertEquals(errorsValue.getType(), "array"); + assertTrue(errorsValue.getNullable()); + assertNotNull(errorsValue.getItems()); + } + } diff --git a/modules/openapi-generator/src/test/resources/3_1/issue_23945.yaml b/modules/openapi-generator/src/test/resources/3_1/issue_23945.yaml new file mode 100644 index 000000000000..b1bb362584d5 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_1/issue_23945.yaml @@ -0,0 +1,22 @@ +openapi: 3.1.1 +info: + title: Repro + version: 1.0.0 +components: + schemas: + NullableMaps: + type: object + properties: + stringMap: + type: [object, "null"] + additionalProperties: + type: "null" + errorsByKey: + type: [object, "null"] + additionalProperties: + type: [array, "null"] + items: + type: object + properties: + code: + type: string