Skip to content

[BUG] [Java] [Spring] openapi-generator generates incorrect type information for multilevel inheritance  #1685

@ErikGrimes

Description

@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
  1. Update DefaultCodegen.createDiscriminator
    to add a model for each Schema that is descended from schemaName.
  2. 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).
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions