Reputation: 7719
I have the following structure
Notification
|
------------------------
| |
SmsNotification EmailNotification
The Notification
contains an enum notificationType
containing either SMS
or EMAIL
. Now I have an Inbox
class, which contains a Notification
.
This is specified in the swagger yml as such (removed some irrelevant code)
definitions:
Notification:
type: "object"
discriminator: "notificationType"
properties:
notificationType:
type: "string"
description: "Type of notification"
enum:
- "EMAIL"
- "SMS"
SmsNotification:
allOf:
- $ref: "#/definitions/Notification"
- type: "object"
EmailNotification
allOf:
- $ref: "#/definitions/Notification"
- type: "object"
Inbox:
type: "object"
properties:
notification:
description: "Latest received notification"
$ref: "#/definitions/Notification"
I generate my code with swagger-codegen v2
(tried v3 & openapi-generator as well) with the following configuration:
<build>
<plugins>
<plugin>
<groupId>io.swagger</groupId>
<artifactId>swagger-codegen-maven-plugin</artifactId>
<version>2.3.1</version>
<executions>
<execution>
<id>notifications</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/notifications/swagger.yaml</inputSpec>
<language>java</language>
<library>jersey2</library>
<generateSupportingFiles>false</generateSupportingFiles>
<modelPackage>${generated.package}</modelPackage>
<generateApis>false</generateApis>
<generateApiDocumentation>false</generateApiDocumentation>
<generateModelTests>false</generateModelTests>
<generateModelDocumentation>false</generateModelDocumentation>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Now what happens is that the jersey2
library will generate JsonSubType
annotations as such:
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.Property, property="notificationType", visible=true)
@JsonSubTypes({
@JsonSubTypes.Type(value=SmsNotification.class, name="SmsNotification"),
@JsonSubTypes.Type(value=EmailNotification.class, name="EmailNotification")
})
public class Notification {
...
}
The problem here is that if I now try to deserialize/serialize a Json string containing an Inbox with the notificationType=EMAIL
, that it will throw an exception since there is no known subtype with the name 'EMAIL'
.
The seralizer expects the JsonSubType
annotations to be specified like this:
(sidenote, this is also how the code looks from which the swagger yaml is generated)
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.Property, property="notificationType", visible=true)
@JsonSubTypes({
@JsonSubTypes.Type(value=SmsNotification.class, name="SMS"),
@JsonSubTypes.Type(value=EmailNotification.class, name="EMAIL")
})
public class Notification {
...
}
Does anyone know how to generate the JsonSubTypes
annotation as desired instead of the current behaviour?
Upvotes: 6
Views: 12979
Reputation: 606
I found a relevant answer in swagger-codegen issue (and checked that it does work) https://github.com/swagger-api/swagger-codegen/issues/1253#issuecomment-277279474
There is a vendor extension "x-discriminator-value" that can be used along with the Jackson annotations to specify the discriminator value to be used instead of the object name.
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({@JsonSubTypes.Type(value = MySubClassOne.class, name = "my-sub-class-one")})
Swagger
definitions: MySubClassOne: x-discriminator-value: my-sub-class-one
Upvotes: 0
Reputation: 1
For anyone with the same issue, what worked for me was to change the original plugin (swagger-codegen-maven-plugin) with the one suggested above (openapi-generator-maven-plugin)
Upvotes: 0
Reputation: 2397
I had a similar issue and fixed it.
I do not maintain the OpenAPI definition directly but use annotations in my beans. Then I generate the OpenAPI definition (JSON format) and then I generate the client project using openapi-generator.
The issue came from the missing DiscriminatorMapping
annotations.
Entity.java
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type",
visible = true
)
@JsonSubTypes({
@JsonSubTypes.Type(value = FooEntity.class, name = "FOO"),
@JsonSubTypes.Type(value = BarEntity.class, name = "BAR")
})
// Without the following annotations I have the same issue than OP
@Schema(
description = "Entity",
discriminatorProperty = "type",
discriminatorMapping = {
@DiscriminatorMapping(schema = FooEntity.class, value = "FOO"),
@DiscriminatorMapping(schema = BarEntity.class, value = "BAR")
}
)
public abstract class Entity {
@Schema(description = "Entity type", required = true)
protected final EntityType type;
}
EntityType.java
@Schema(description = "Entity type")
public enum EntityType {
FOO,
BAR
}
FooEntity.java (BarEntity.java works the same way).
@Schema(description = "Foo entity")
public class FooEntity extends Entity {
public FooEntity() {
super(EntityType.FOO);
}
}
// ...
"schemas": {
"Entity": {
"required": [
"type"
],
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"FOO",
"BAR"
]
}
},
"description": "Entity",
"discriminator": {
"propertyName": "type",
"mapping": {
"FOO": "#/components/schemas/FooEntity",
"BAR": "#/components/schemas/BarEntity"
}
}
},
"FooEntity": {
"required": [
"type"
],
"type": "object",
"description": "Foo entity",
"allOf": [
{
"$ref": "#/components/schemas/Entity"
}
]
},
}
// ...
Generated using the following plugin in my pom.xml
<plugin>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-maven-plugin</artifactId>
<version>2.1.2</version>
<executions>
<execution>
<id>generate-api-definition</id>
<phase>compile</phase>
<goals>
<goal>resolve</goal>
</goals>
</execution>
</executions>
<configuration>
<configurationFilePath>${project.build.directory}/api/WEB-INF/openapi-configuration.json</configurationFilePath> <!-- My configuration file -->
<outputPath>${project.build.directory}/api/WEB-INF</outputPath>
<outputFileName>openapi</outputFileName>
<outputFormat>JSON</outputFormat>
</configuration>
</plugin>
//...
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2020-05-07T15:17:15.844882+02:00[Europe/Paris]")
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = FooEntity.class, name = "FOO"),
@JsonSubTypes.Type(value = BarEntity.class, name = "BAR")
})
public class Entity {
public enum TypeEnum {
FOO("FOO"),
BAR("BAR");
// ...
}
public static final String JSON_PROPERTY_TYPE = "type";
protected TypeEnum type;
public Entity type(TypeEnum type) {
this.type = type;
return this;
}
@ApiModelProperty(required = true, value = "Entity type")
@JsonProperty(JSON_PROPERTY_TYPE)
@JsonInclude(value = JsonInclude.Include.ALWAYS)
public TypeEnum getType() {
return type;
}
public void setType(TypeEnum type) {
this.type = type;
}
// ...
}
Generator plugin :
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>4.3.1</version>
<executions>
<execution>
<id>generate-client</id>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<!-- https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-maven-plugin -->
<inputSpec>x/y/z/openapi.json</inputSpec>
<addCompileSourceRoot>false</addCompileSourceRoot>
<generatorName>java</generatorName>
<configOptions>
<java8>true</java8>
<serializableModel>true</serializableModel>
<serializationLibrary>jackson</serializationLibrary>
<library>jersey2</library>
<dateLibrary>java8</dateLibrary>
<!-- ... -->
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
Upvotes: 8