Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{{#jackson}}
@JsonIgnoreProperties(
value = "{{{discriminator.propertyBaseName}}}", // ignore manually set {{{discriminator.propertyBaseName}}}, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the {{{discriminator.propertyBaseName}}} to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = true)
@JsonSubTypes({
{{#discriminator.mappedModels}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 +1289,88 @@ public void testDiscriminatorMappingUsedInJsonTypeName() throws Exception {
.fileDoesNotContain("@JsonTypeName(\"DogRequest\")");
}

/**
* A model with a discriminator must emit {@code @JsonIgnoreProperties} on the discriminator
* property so Jackson does not serialize that property twice (once for the manually declared
* field and once for the {@code @JsonTypeInfo} type id), which produces a duplicate key in the
* response body. {@code allowSetters = true} preserves the field during deserialization.
*/
@Test
public void testDiscriminatorEmitsJsonIgnorePropertiesToAvoidDuplicateKey() throws Exception {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/jaxrs/petstore.yaml", null, new ParseOptions()).getOpenAPI();

codegen.setOutputDir(output.getAbsolutePath());

ClientOptInput input = new ClientOptInput()
.openAPI(openAPI)
.config(codegen);

DefaultGenerator generator = new DefaultGenerator();
Map<String, File> files = generator.opts(input).generate().stream()
.collect(Collectors.toMap(File::getName, Function.identity()));

// The parent model (which carries the discriminator) must ignore the manually declared
// discriminator property during serialization while still allowing it to be set during
// deserialization. The discriminator property in this spec is "petType".
JavaFileAssert.assertThat(files.get("PetRequest.java"))
.hasImports("com.fasterxml.jackson.annotation.JsonIgnoreProperties")
.fileContains(
"@JsonIgnoreProperties(",
"value = \"petType\"",
"allowSetters = true")
// @JsonIgnoreProperties must precede @JsonTypeInfo on the class declaration.
.fileContainsPattern("@JsonIgnoreProperties\\([\\s\\S]*?@JsonTypeInfo");

// Child models do not carry the discriminator, so they must not receive the annotation.
JavaFileAssert.assertThat(files.get("CatRequest.java"))
.fileDoesNotContain("@JsonIgnoreProperties");
JavaFileAssert.assertThat(files.get("DogRequest.java"))
.fileDoesNotContain("@JsonIgnoreProperties");
}

/**
* With {@code legacyDiscriminatorBehavior=false} the discriminator is propagated from the
* parent onto every child reachable through the discriminator mapping, so each child also
* emits {@code @JsonTypeInfo}. The {@code @JsonIgnoreProperties} fix must therefore apply to
* the children too, otherwise they would serialize the discriminator property twice.
*/
@Test
public void testDiscriminatorJsonIgnorePropertiesPropagatesToChildren_whenLegacyBehaviorDisabled() throws Exception {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

Map<String, Object> properties = new HashMap<>();
properties.put("legacyDiscriminatorBehavior", "false");

final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("jaxrs-spec")
.setAdditionalProperties(properties)
.setInputSpec("src/test/resources/3_0/jaxrs-spec/discriminator-mapping-children.yaml")
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));

DefaultGenerator generator = new DefaultGenerator();
Map<String, File> files = generator.opts(configurator.toClientOptInput()).generate().stream()
.collect(Collectors.toMap(File::getName, Function.identity()));

// The parent and every mapped child must ignore the manually declared discriminator
// property during serialization (it is written once by @JsonTypeInfo) while still
// allowing it to be set during deserialization. The discriminator property is "petType".
for (String modelFile : new String[]{"PetResponse.java", "CatResponse.java", "DogResponse.java"}) {
JavaFileAssert.assertThat(files.get(modelFile))
.hasImports("com.fasterxml.jackson.annotation.JsonIgnoreProperties")
.fileContains(
"@JsonIgnoreProperties(",
"value = \"petType\"",
"allowSetters = true")
// @JsonIgnoreProperties must precede @JsonTypeInfo on the class declaration.
.fileContainsPattern("@JsonIgnoreProperties\\([\\s\\S]*?@JsonTypeInfo");
}
}

@Test
public void testGenerateJsonNullableListFieldsHelperMethodReferences_issue23251() throws Exception {
Map<String, Object> properties = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
openapi: 3.0.3
info:
title: Discriminator mapping with allOf children
description: >
Minimal spec reproducing a discriminated polymorphic response where the
discriminator declares an explicit mapping to child schemas. With
legacyDiscriminatorBehavior=false the discriminator is propagated onto the
mapped children, so the @JsonIgnoreProperties / @JsonTypeInfo annotations
must be emitted on both the parent and the children.
version: 1.0.0
paths:
/pets:
get:
operationId: getPet
responses:
'200':
description: A pet
content:
application/json:
schema:
$ref: '#/components/schemas/PetResponse'
components:
schemas:
PetResponse:
type: object
required:
- petType
properties:
petType:
type: string
name:
type: string
discriminator:
propertyName: petType
mapping:
CAT: '#/components/schemas/CatResponse'
DOG: '#/components/schemas/DogResponse'
CatResponse:
description: Response payload for a cat resource
allOf:
- $ref: '#/components/schemas/PetResponse'
- type: object
properties:
declawed:
type: boolean
DogResponse:
description: Response payload for a dog resource
allOf:
- $ref: '#/components/schemas/PetResponse'
- type: object
properties:
bark:
type: boolean
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.openapitools.jackson.nullable.JsonNullable;

@JsonIgnoreProperties(
value = "className", // ignore manually set className, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the className to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "className", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = BigCat.class, name = "BigCat"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.openapitools.jackson.nullable.JsonNullable;

@JsonIgnoreProperties(
value = "className", // ignore manually set className, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the className to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "className", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Cat.class, name = "CAT"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.openapitools.jackson.nullable.JsonNullable;

@JsonIgnoreProperties(
value = "type", // ignore manually set type, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the type to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = ChildWithNullable.class, name = "ChildWithNullable"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.openapitools.jackson.nullable.JsonNullable;

@JsonIgnoreProperties(
value = "className", // ignore manually set className, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the className to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "className", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Cat.class, name = "CAT"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.openapitools.jackson.nullable.JsonNullable;

@JsonIgnoreProperties(
value = "type", // ignore manually set type, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the type to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = ChildWithNullable.class, name = "ChildWithNullable"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.openapitools.jackson.nullable.JsonNullable;

@JsonIgnoreProperties(
value = "className", // ignore manually set className, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the className to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "className", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Cat.class, name = "CAT"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.openapitools.jackson.nullable.JsonNullable;

@JsonIgnoreProperties(
value = "type", // ignore manually set type, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the type to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = ChildWithNullable.class, name = "ChildWithNullable"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.openapitools.jackson.nullable.JsonNullable;

@JsonIgnoreProperties(
value = "className", // ignore manually set className, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the className to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "className", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Cat.class, name = "CAT"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.openapitools.jackson.nullable.JsonNullable;

@JsonIgnoreProperties(
value = "type", // ignore manually set type, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the type to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = ChildWithNullable.class, name = "ChildWithNullable"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlEnumValue;

@JsonIgnoreProperties(
value = "className", // ignore manually set className, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the className to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "className", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Cat.class, name = "CAT"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlEnumValue;

@JsonIgnoreProperties(
value = "type", // ignore manually set type, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the type to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = ChildWithNullable.class, name = "ChildWithNullable"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.openapitools.jackson.nullable.JsonNullable;

@JsonIgnoreProperties(
value = "className", // ignore manually set className, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the className to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "className", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Cat.class, name = "CAT"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.openapitools.jackson.nullable.JsonNullable;

@JsonIgnoreProperties(
value = "type", // ignore manually set type, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the type to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = ChildWithNullable.class, name = "ChildWithNullable"),
Expand Down
Loading