[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.createDiscriminatorto add a model for eachSchemathat is descended fromschemaName. - Allow the schema consumer to provide configuration to the code generator to obtain the desired
@JsonSubTypesbehavior 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@MappedSuperclassand@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