[BUG] [Java] [Spring] openapi-generator generates incorrect type information for multilevel inheritance
Created by: ErikGrimes
Description
The openapi-generator spring generator generates incorrect @JsonSubTypes for multilevel inheritance
class Animal
class Dog extends Animal
class BigDog extends Dog
class Cat extends Animal
openapi-generator version
3.3.4
OpenAPI declaration file content or url
api.yaml
openapi: 3.0.0
info:
title: Sample API
description: API description in Markdown.
version: 1.0.0
paths:
/animals:
get:
summary: Returns all animals.
description: Optional extended description in Markdown.
responses:
200:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Animal'
components:
schemas:
Dog:
allOf:
- $ref: '#/components/schemas/Animal'
- type: object
properties:
breed:
type: string
Cat:
allOf:
- $ref: '#/components/schemas/Animal'
- type: object
properties:
breed:
type: string
BigDog:
allOf:
- $ref: '#/components/schemas/Dog'
- type: object
discriminator:
propertyName: dogType
required:
- dogType
properties:
dogType:
type: string
declawed:
type: boolean
Animal:
type: object
discriminator:
propertyName: className
required:
- className
properties:
className:
type: string
color:
type: string
default: red
Command line used for generation
openapi-generator generate -g spring -i api.yaml -o generated -DdelegatePattern=true,hideGenerationTimestamp=true
Steps to reproduce
Invoke the command line about with the provided api.yaml
Expected Output:
Animal.java
package org.openapitools.model;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.Valid;
import javax.validation.constraints.*;
/**
* Animal
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "className", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "BigDog"),
@JsonSubTypes.Type(value = Cat.class, name = "Cat"),
})
public class Animal {
@JsonProperty("className")
private String className;
@JsonProperty("color")
private String color = "red";
public Animal className(String className) {
this.className = className;
return this;
}
/**
* Get className
* @return className
*/
@ApiModelProperty(required = true, value = "")
@NotNull
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public Animal color(String color) {
this.color = color;
return this;
}
/**
* Get color
* @return color
*/
@ApiModelProperty(value = "")
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public boolean equals(java.lang.Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Animal animal = (Animal) o;
return Objects.equals(this.className, animal.className) &&
Objects.equals(this.color, animal.color);
}
@Override
public int hashCode() {
return Objects.hash(className, color);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class Animal {\n");
sb.append(" className: ").append(toIndentedString(className)).append("\n");
sb.append(" color: ").append(toIndentedString(color)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private String toIndentedString(java.lang.Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}
Dog.java
public abstract class Dog extends Animal {
}
BigDog.java
public class BigDog extends Dog {
}
Cat.java
public class Cat extends Animal {
}
Actual Output
Animal.java
package org.openapitools.model;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.Valid;
import javax.validation.constraints.*;
/**
* Animal
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "className", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "Dog"),
@JsonSubTypes.Type(value = Cat.class, name = "Cat"),
})
public class Animal {
@JsonProperty("className")
private String className;
@JsonProperty("color")
private String color = "red";
public Animal className(String className) {
this.className = className;
return this;
}
/**
* Get className
* @return className
*/
@ApiModelProperty(required = true, value = "")
@NotNull
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public Animal color(String color) {
this.color = color;
return this;
}
/**
* Get color
* @return color
*/
@ApiModelProperty(value = "")
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public boolean equals(java.lang.Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Animal animal = (Animal) o;
return Objects.equals(this.className, animal.className) &&
Objects.equals(this.color, animal.color);
}
@Override
public int hashCode() {
return Objects.hash(className, color);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class Animal {\n");
sb.append(" className: ").append(toIndentedString(className)).append("\n");
sb.append(" color: ").append(toIndentedString(color)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private String toIndentedString(java.lang.Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}
Dog.java
public class Dog extends Animal {
}
BigDog.java
public class BigDog extends Dog {
}
Cat.java
public class Cat extends Animal {
}
Suggest a fix
- Update
DefaultCodegen.createDiscriminator
to add a model for eachSchema
that is descended fromschemaName
. - Allow the schema consumer to provide configuration to the code generator to obtain the desired
@JsonSubTypes
behavior either by designating the desired values explicitly or by indicating the models in the inheritance hierarchy that should be abstract and ignored (similar to the way that JPA uses@MappedSuperclass
and@Entity)
. - Update the Maven plugin to support the new config options.
mappedSupermodels
openapi-generator generate -g spring -i api.yaml -o generated -DdelegatePattern=true,hideGenerationTimestamp=true -DmappedSupermodel=Dog
subtypeModels
openapi-generator generate -g spring -i api.yaml -o generated -DdelegatePattern=true,hideGenerationTimestamp=true -DsubtypeModels=Cat,BigDog