Skip to content

Commit 8ce639d

Browse files
[kotlin-client][kotlin-spring] Fix duplicate discriminator serialization with Jackson used as serialization library (#21734)
* [kotlin-client][kotlin-spring] Fix duplicate discriminator serialization with Jackson used as serialization library * Update samples
1 parent ca7e8bd commit 8ce639d

File tree

7 files changed

+108
-11
lines changed

7 files changed

+108
-11
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import com.samskivert.mustache.Mustache;
2121
import com.samskivert.mustache.Mustache.Lambda;
2222
import com.samskivert.mustache.Template;
23-
import io.swagger.v3.oas.models.Components;
2423
import io.swagger.v3.oas.models.OpenAPI;
2524
import io.swagger.v3.oas.models.Operation;
2625
import lombok.Getter;
@@ -425,6 +424,7 @@ public void processOpts() {
425424
importMapping.put("JsonProperty", "com.fasterxml.jackson.annotation.JsonProperty");
426425
importMapping.put("JsonSubTypes", "com.fasterxml.jackson.annotation.JsonSubTypes");
427426
importMapping.put("JsonTypeInfo", "com.fasterxml.jackson.annotation.JsonTypeInfo");
427+
importMapping.put("JsonIgnoreProperties", "com.fasterxml.jackson.annotation.JsonIgnoreProperties");
428428
// import JsonCreator if JsonProperty is imported
429429
// used later in recursive import in postProcessingModels
430430
importMapping.put("com.fasterxml.jackson.annotation.JsonProperty", "com.fasterxml.jackson.annotation.JsonCreator");
@@ -827,7 +827,7 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty propert
827827
}
828828

829829
if (model.discriminator != null && additionalProperties.containsKey("jackson")) {
830-
model.imports.addAll(Arrays.asList("JsonSubTypes", "JsonTypeInfo"));
830+
model.imports.addAll(Arrays.asList("JsonSubTypes", "JsonTypeInfo", "JsonIgnoreProperties"));
831831
}
832832
}
833833

modules/openapi-generator/src/main/resources/kotlin-client/data_class.mustache

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonEnumDefaultValue
2323
{{/enumUnknownDefaultCase}}
2424
import com.fasterxml.jackson.annotation.JsonProperty
2525
{{#discriminator}}
26+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
2627
import com.fasterxml.jackson.annotation.JsonSubTypes
2728
import com.fasterxml.jackson.annotation.JsonTypeInfo
2829
{{/discriminator}}

modules/openapi-generator/src/main/resources/kotlin-client/typeInfoAnnotation.mustache

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
@JsonIgnoreProperties(
2+
value = ["{{{discriminator.propertyBaseName}}}"], // ignore manually set {{{discriminator.propertyBaseName}}}, it will be automatically generated by Jackson during serialization
3+
allowSetters = true // allows the {{{discriminator.propertyBaseName}}} to be set during deserialization
4+
)
15
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = true)
26
@JsonSubTypes(
37
{{#discriminator.mappedModels}}

modules/openapi-generator/src/main/resources/kotlin-spring/typeInfoAnnotation.mustache

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
{{#jackson}}
2-
2+
@JsonIgnoreProperties(
3+
value = ["{{{discriminator.propertyBaseName}}}"], // ignore manually set {{{discriminator.propertyBaseName}}}, it will be automatically generated by Jackson during serialization
4+
allowSetters = true // allows the {{{discriminator.propertyBaseName}}} to be set during deserialization
5+
)
36
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = true)
47
@JsonSubTypes(
58
{{#discriminator.mappedModels}}

modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,15 @@
1919

2020
import io.swagger.v3.oas.models.OpenAPI;
2121
import io.swagger.v3.oas.models.media.*;
22-
import lombok.Getter;
23-
import org.antlr.v4.runtime.*;
22+
import org.antlr.v4.runtime.CharStreams;
23+
import org.antlr.v4.runtime.CommonTokenStream;
2424
import org.antlr.v4.runtime.tree.ParseTree;
2525
import org.antlr.v4.runtime.tree.ParseTreeWalker;
2626
import org.openapitools.codegen.*;
2727
import org.openapitools.codegen.antlr4.KotlinLexer;
2828
import org.openapitools.codegen.antlr4.KotlinParser;
29-
import org.openapitools.codegen.antlr4.KotlinParserBaseListener;
3029
import org.openapitools.codegen.config.CodegenConfigurator;
3130
import org.openapitools.codegen.languages.KotlinClientCodegen;
32-
import org.openapitools.codegen.languages.KotlinServerCodegen;
3331
import org.openapitools.codegen.testutils.ConfigAssert;
3432
import org.testng.Assert;
3533
import org.testng.annotations.DataProvider;
@@ -45,8 +43,6 @@
4543
import java.util.Map;
4644

4745
import static org.openapitools.codegen.CodegenConstants.*;
48-
import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.INTERFACE_ONLY;
49-
import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.RETURN_RESPONSE;
5046

5147
@SuppressWarnings("static-method")
5248
public class KotlinClientCodegenModelTest {
@@ -534,7 +530,7 @@ private void givenSchemaObjectPropertyNameContainsDollarSignWhenGenerateThenDoll
534530
}
535531

536532
@Test(description = "generate polymorphic kotlinx_serialization model")
537-
public void polymorphicKotlinxSerialzation() throws IOException {
533+
public void polymorphicKotlinxSerialization() throws IOException {
538534
File output = Files.createTempDirectory("test").toFile();
539535
output.deleteOnExit();
540536

@@ -573,6 +569,54 @@ public void polymorphicKotlinxSerialzation() throws IOException {
573569
TestUtils.assertFileContains(birdKt, "@SerialName(value = \"BIRD\")");
574570
}
575571

572+
@Test(description = "generate polymorphic jackson model")
573+
public void polymorphicJacksonSerialization() throws IOException {
574+
File output = Files.createTempDirectory("test").toFile();
575+
// output.deleteOnExit();
576+
577+
final CodegenConfigurator configurator = new CodegenConfigurator()
578+
.setGeneratorName("kotlin")
579+
.setLibrary("jvm-okhttp4")
580+
.setAdditionalProperties(new HashMap<>() {{
581+
put(CodegenConstants.SERIALIZATION_LIBRARY, "jackson");
582+
put(CodegenConstants.MODEL_PACKAGE, "xyz.abcdef.model");
583+
}})
584+
.setInputSpec("src/test/resources/3_0/kotlin/polymorphism.yaml")
585+
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
586+
587+
final ClientOptInput clientOptInput = configurator.toClientOptInput();
588+
DefaultGenerator generator = new DefaultGenerator();
589+
List<File> files = generator.opts(clientOptInput).generate();
590+
591+
Assert.assertEquals(files.size(), 28);
592+
593+
final Path animalKt = Paths.get(output + "/src/main/kotlin/xyz/abcdef/model/Animal.kt");
594+
// base has extra jackson imports
595+
TestUtils.assertFileContains(animalKt, "import com.fasterxml.jackson.annotation.JsonIgnoreProperties");
596+
TestUtils.assertFileContains(animalKt, "import com.fasterxml.jackson.annotation.JsonSubTypes");
597+
TestUtils.assertFileContains(animalKt, "import com.fasterxml.jackson.annotation.JsonTypeInfo");
598+
// and these are being used
599+
TestUtils.assertFileContains(animalKt, "@JsonIgnoreProperties");
600+
TestUtils.assertFileContains(animalKt, "@JsonSubTypes");
601+
TestUtils.assertFileContains(animalKt, "@JsonTypeInfo");
602+
// base is interface
603+
TestUtils.assertFileContains(animalKt, "interface Animal");
604+
// base properties are present
605+
TestUtils.assertFileContains(animalKt, "val id");
606+
TestUtils.assertFileContains(animalKt, "val optionalProperty");
607+
// base doesn't contain discriminator
608+
TestUtils.assertFileNotContains(animalKt, "val discriminator");
609+
610+
final Path birdKt = Paths.get(output + "/src/main/kotlin/xyz/abcdef/model/Bird.kt");
611+
// derived has serial name set to mapping key
612+
TestUtils.assertFileContains(birdKt, "data class Bird");
613+
// derived properties are overridden
614+
TestUtils.assertFileContains(birdKt, "override val id");
615+
TestUtils.assertFileContains(birdKt, "override val optionalProperty");
616+
// derived doesn't contain disciminator
617+
TestUtils.assertFileNotContains(birdKt, "val discriminator");
618+
}
619+
576620
private static class ModelNameTest {
577621
private final String expectedName;
578622
private final String expectedClassName;

modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinSpringServerCodegenTest.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,45 @@ public void gradleWrapperIsGenerated() throws IOException {
6060
//Different because file is not a text file
6161
assertTrue(Files.exists(gradleWrapperJarCloud));
6262
}
63+
64+
@Test(description = "generate polymorphic jackson model")
65+
public void polymorphicJacksonSerialization() throws IOException {
66+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
67+
output.deleteOnExit();
68+
69+
KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen() ;
70+
codegen.setOutputDir(output.getAbsolutePath());
71+
72+
new DefaultGenerator().opts(
73+
new ClientOptInput()
74+
.openAPI(TestUtils.parseSpec("src/test/resources/3_0/kotlin/polymorphism.yaml"))
75+
.config(codegen)
76+
).generate();
77+
78+
final Path animalKt = Paths.get(output + "/src/main/kotlin/org/openapitools/model/Animal.kt");
79+
// base has extra jackson imports
80+
TestUtils.assertFileContains(animalKt, "import com.fasterxml.jackson.annotation.JsonIgnoreProperties");
81+
TestUtils.assertFileContains(animalKt, "import com.fasterxml.jackson.annotation.JsonSubTypes");
82+
TestUtils.assertFileContains(animalKt, "import com.fasterxml.jackson.annotation.JsonTypeInfo");
83+
// and these are being used
84+
TestUtils.assertFileContains(animalKt, "@JsonIgnoreProperties");
85+
TestUtils.assertFileContains(animalKt, "@JsonSubTypes");
86+
TestUtils.assertFileContains(animalKt, "@JsonTypeInfo");
87+
// base is interface
88+
TestUtils.assertFileContains(animalKt, "interface Animal");
89+
// base properties are present
90+
TestUtils.assertFileContains(animalKt, "val id");
91+
TestUtils.assertFileContains(animalKt, "val optionalProperty");
92+
// base doesn't contain discriminator
93+
TestUtils.assertFileNotContains(animalKt, "val discriminator");
94+
95+
final Path birdKt = Paths.get(output + "/src/main/kotlin/org/openapitools/model/Bird.kt");
96+
// derived has serial name set to mapping key
97+
TestUtils.assertFileContains(birdKt, "data class Bird");
98+
// derived properties are overridden
99+
TestUtils.assertFileContains(birdKt, "override val id");
100+
TestUtils.assertFileContains(birdKt, "override val optionalProperty");
101+
// derived doesn't contain disciminator
102+
TestUtils.assertFileNotContains(birdKt, "val discriminator");
103+
}
63104
}

samples/server/petstore/kotlin-springboot-request-cookie/src/main/kotlin/org/openapitools/model/Animal.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.openapitools.model
22

33
import java.util.Objects
4+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
45
import com.fasterxml.jackson.annotation.JsonProperty
56
import com.fasterxml.jackson.annotation.JsonSubTypes
67
import com.fasterxml.jackson.annotation.JsonTypeInfo
@@ -20,7 +21,10 @@ import io.swagger.v3.oas.annotations.media.Schema
2021
* @param className
2122
* @param color
2223
*/
23-
24+
@JsonIgnoreProperties(
25+
value = ["className"], // ignore manually set className, it will be automatically generated by Jackson during serialization
26+
allowSetters = true // allows the className to be set during deserialization
27+
)
2428
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "className", visible = true)
2529
@JsonSubTypes(
2630
JsonSubTypes.Type(value = Cat::class, name = "CAT"),

0 commit comments

Comments
 (0)