From ed901323d8345515ea247048b28f774737fcc21c Mon Sep 17 00:00:00 2001 From: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com> Date: Sun, 7 Jun 2026 21:11:54 +0900 Subject: [PATCH] [BUG] Normalize OAS 3.1 `type: null` map value under NORMALIZE_31SPEC A nullable map whose `additionalProperties` value schema is `type: "null"` (or `type: [array, "null"]`) was not normalized to a usable OAS 3.0 value type. Generators emitted a fictional `Null` / `ModelNull` value type that fails to compile (e.g. `Map` with no `ModelNull` model). A pure `type: "null"` value schema is short-circuited by `ModelUtils.isNullTypeSchema` before normalization, so it kept its OAS 3.1 null type. A `type: [array, "null"]` value was converted to a new array schema whose result was then discarded by the map branch. When NORMALIZE_31SPEC is enabled, rewrite a pure-null map value to an any-type nullable schema and capture the normalized value schema for the array case. Behavior is unchanged unless NORMALIZE_31SPEC is enabled. Fixes #23945 Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com> --- .../codegen/OpenAPINormalizer.java | 19 +++++++++++++- .../codegen/OpenAPINormalizerTest.java | 25 +++++++++++++++++++ .../src/test/resources/3_1/issue_23945.yaml | 22 ++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 modules/openapi-generator/src/test/resources/3_1/issue_23945.yaml 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