From 4f08ee1b982bd272f6487a7a604286b1cc1e3141 Mon Sep 17 00:00:00 2001 From: Inf1mum Date: Tue, 3 Nov 2026 20:25:55 +0300 Subject: [PATCH 1/2] [kotlin-server] fix: use As.EXISTING_PROPERTY for discriminator type info Every mapped child model declares or inherits the discriminator property (KotlinServerCodegen guarantees that), so As.PROPERTY made Jackson serialize the discriminator twice - once injected, once as the regular property - producing invalid JSON with a duplicated key. Fixes #25524 --- .../resources/kotlin-server/typeInfoAnnotation.mustache | 2 +- .../codegen/kotlin/KotlinServerCodegenTest.java | 7 +++++++ .../src/main/kotlin/org/openapitools/server/models/Pet.kt | 2 +- .../src/main/kotlin/org/openapitools/server/models/Pet.kt | 2 +- .../src/main/kotlin/org/openapitools/server/models/Pet.kt | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/kotlin-server/typeInfoAnnotation.mustache b/modules/openapi-generator/src/main/resources/kotlin-server/typeInfoAnnotation.mustache index 88fddd2d51cb..83df0336562e 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-server/typeInfoAnnotation.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-server/typeInfoAnnotation.mustache @@ -1,4 +1,4 @@ -@com.fasterxml.jackson.annotation.JsonTypeInfo(use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = {{#vendorExtensions.x-discriminator-visible-true}}true{{/vendorExtensions.x-discriminator-visible-true}}{{^vendorExtensions.x-discriminator-visible-true}}false{{/vendorExtensions.x-discriminator-visible-true}}) +@com.fasterxml.jackson.annotation.JsonTypeInfo(use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.EXISTING_PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = {{#vendorExtensions.x-discriminator-visible-true}}true{{/vendorExtensions.x-discriminator-visible-true}}{{^vendorExtensions.x-discriminator-visible-true}}false{{/vendorExtensions.x-discriminator-visible-true}}) @com.fasterxml.jackson.annotation.JsonSubTypes( {{#discriminator.mappedModels}} com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = {{modelName}}::class, name = "{{^vendorExtensions.x-discriminator-value}}{{mappingName}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}"){{^-last}},{{/-last}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinServerCodegenTest.java index f931f659b431..f92397a9af1b 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinServerCodegenTest.java @@ -405,10 +405,17 @@ public void oneOfWithDiscriminator_generatesSealedClassWithDiscriminatorProperty "sealed class Pet(", "open val petType: kotlin.String", "@com.fasterxml.jackson.annotation.JsonTypeInfo", + // every mapped child model declares/inherits the discriminator property, so + // Jackson must reuse it instead of injecting a second copy (duplicated key) + "include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.EXISTING_PROPERTY", "property = \"petType\"", "visible = true", "@com.fasterxml.jackson.annotation.JsonSubTypes" ); + assertFileNotContains( + petModel, + "include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY," + ); } @Test diff --git a/samples/server/others/kotlin-server/polymorphism-allof-and-discriminator/src/main/kotlin/org/openapitools/server/models/Pet.kt b/samples/server/others/kotlin-server/polymorphism-allof-and-discriminator/src/main/kotlin/org/openapitools/server/models/Pet.kt index 5812ac1944eb..b50931bae500 100644 --- a/samples/server/others/kotlin-server/polymorphism-allof-and-discriminator/src/main/kotlin/org/openapitools/server/models/Pet.kt +++ b/samples/server/others/kotlin-server/polymorphism-allof-and-discriminator/src/main/kotlin/org/openapitools/server/models/Pet.kt @@ -17,7 +17,7 @@ package org.openapitools.server.models * @param name * @param petType */ -@com.fasterxml.jackson.annotation.JsonTypeInfo(use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY, property = "petType", visible = true) +@com.fasterxml.jackson.annotation.JsonTypeInfo(use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.EXISTING_PROPERTY, property = "petType", visible = true) @com.fasterxml.jackson.annotation.JsonSubTypes( com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = Cat::class, name = "Cat"), com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = Dog::class, name = "Dog") diff --git a/samples/server/others/kotlin-server/polymorphism-and-discriminator-disabled-jackson-fix/src/main/kotlin/org/openapitools/server/models/Pet.kt b/samples/server/others/kotlin-server/polymorphism-and-discriminator-disabled-jackson-fix/src/main/kotlin/org/openapitools/server/models/Pet.kt index 737a74aefcfb..dca53070b79c 100644 --- a/samples/server/others/kotlin-server/polymorphism-and-discriminator-disabled-jackson-fix/src/main/kotlin/org/openapitools/server/models/Pet.kt +++ b/samples/server/others/kotlin-server/polymorphism-and-discriminator-disabled-jackson-fix/src/main/kotlin/org/openapitools/server/models/Pet.kt @@ -17,7 +17,7 @@ import org.openapitools.server.models.Dog /** * A pet */ -@com.fasterxml.jackson.annotation.JsonTypeInfo(use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY, property = "petType", visible = false) +@com.fasterxml.jackson.annotation.JsonTypeInfo(use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.EXISTING_PROPERTY, property = "petType", visible = false) @com.fasterxml.jackson.annotation.JsonSubTypes( com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = Cat::class, name = "cat"), com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = Dog::class, name = "dog") diff --git a/samples/server/others/kotlin-server/polymorphism-and-discriminator/src/main/kotlin/org/openapitools/server/models/Pet.kt b/samples/server/others/kotlin-server/polymorphism-and-discriminator/src/main/kotlin/org/openapitools/server/models/Pet.kt index 30ada8deb372..191021901fee 100644 --- a/samples/server/others/kotlin-server/polymorphism-and-discriminator/src/main/kotlin/org/openapitools/server/models/Pet.kt +++ b/samples/server/others/kotlin-server/polymorphism-and-discriminator/src/main/kotlin/org/openapitools/server/models/Pet.kt @@ -18,7 +18,7 @@ import org.openapitools.server.models.Dog * A pet * @param petType */ -@com.fasterxml.jackson.annotation.JsonTypeInfo(use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY, property = "petType", visible = true) +@com.fasterxml.jackson.annotation.JsonTypeInfo(use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.EXISTING_PROPERTY, property = "petType", visible = true) @com.fasterxml.jackson.annotation.JsonSubTypes( com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = Cat::class, name = "cat"), com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = Dog::class, name = "dog") From fde104a3cc4114cf7e38f85e14a7534bcab4deca Mon Sep 17 00:00:00 2001 From: Inf1mum Date: Tue, 3 Nov 2026 23:46:30 +0300 Subject: [PATCH 2/2] [kotlin-server] make discriminator visible so round-trips preserve it With As.EXISTING_PROPERTY the discriminator is read from and written to the real property that every mapped child model declares or inherits. Emit visible=true unconditionally so the property is also populated on deserialization - otherwise a deserialized child (in the fixJacksonJsonTypeInfoInheritance=false path) would serialize without its discriminator. This is the canonical Jackson pattern for a discriminator modeled as a real property. Addresses review feedback in #25543. --- .../resources/kotlin-server/typeInfoAnnotation.mustache | 2 +- .../codegen/kotlin/KotlinServerCodegenTest.java | 9 ++++++--- .../main/kotlin/org/openapitools/server/models/Pet.kt | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/kotlin-server/typeInfoAnnotation.mustache b/modules/openapi-generator/src/main/resources/kotlin-server/typeInfoAnnotation.mustache index 83df0336562e..ebe8826b0edb 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-server/typeInfoAnnotation.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-server/typeInfoAnnotation.mustache @@ -1,4 +1,4 @@ -@com.fasterxml.jackson.annotation.JsonTypeInfo(use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.EXISTING_PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = {{#vendorExtensions.x-discriminator-visible-true}}true{{/vendorExtensions.x-discriminator-visible-true}}{{^vendorExtensions.x-discriminator-visible-true}}false{{/vendorExtensions.x-discriminator-visible-true}}) +@com.fasterxml.jackson.annotation.JsonTypeInfo(use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.EXISTING_PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = true) @com.fasterxml.jackson.annotation.JsonSubTypes( {{#discriminator.mappedModels}} com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = {{modelName}}::class, name = "{{^vendorExtensions.x-discriminator-value}}{{mappingName}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}"){{^-last}},{{/-last}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinServerCodegenTest.java index f92397a9af1b..1cea927f1de3 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinServerCodegenTest.java @@ -578,12 +578,15 @@ public void fixJacksonJsonTypeInfoInheritance_canBeDisabled() throws IOException String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/server"; Path petModel = Paths.get(outputPath + "/models/Pet.kt"); - // When fixJacksonJsonTypeInfoInheritance is false and parent has no properties, - // visible should be false for oneOf pattern + // With As.EXISTING_PROPERTY the discriminator is read from/written to the real + // property, which every mapped child model declares or inherits; visible=true + // keeps that property populated on deserialization so round-trips preserve it assertFileContains( petModel, - "visible = false" + "include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.EXISTING_PROPERTY", + "visible = true" ); + assertFileNotContains(petModel, "visible = false"); } // ==================== useTags for JAXRS-SPEC ==================== diff --git a/samples/server/others/kotlin-server/polymorphism-and-discriminator-disabled-jackson-fix/src/main/kotlin/org/openapitools/server/models/Pet.kt b/samples/server/others/kotlin-server/polymorphism-and-discriminator-disabled-jackson-fix/src/main/kotlin/org/openapitools/server/models/Pet.kt index dca53070b79c..9f0d58eacfe8 100644 --- a/samples/server/others/kotlin-server/polymorphism-and-discriminator-disabled-jackson-fix/src/main/kotlin/org/openapitools/server/models/Pet.kt +++ b/samples/server/others/kotlin-server/polymorphism-and-discriminator-disabled-jackson-fix/src/main/kotlin/org/openapitools/server/models/Pet.kt @@ -17,7 +17,7 @@ import org.openapitools.server.models.Dog /** * A pet */ -@com.fasterxml.jackson.annotation.JsonTypeInfo(use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.EXISTING_PROPERTY, property = "petType", visible = false) +@com.fasterxml.jackson.annotation.JsonTypeInfo(use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.EXISTING_PROPERTY, property = "petType", visible = true) @com.fasterxml.jackson.annotation.JsonSubTypes( com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = Cat::class, name = "cat"), com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = Dog::class, name = "dog")