Reputation: 38142
We have a class hierarchy like the following:
BaseClass:
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Getter;
import lombok.Setter;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = Child1Class.class, name = "Child1Class"),
@JsonSubTypes.Type(value = Child2Class.class, name = "Child2Class"),
@JsonSubTypes.Type(value = Child3Class.class, name = "Child3Class")
})
@Getter
@Setter
public abstract class BaseClass {
private String type = getClass().getSimpleName();
}
IntermediateClass extending BaseClass:
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.Getter;
import lombok.Setter;
@JsonPropertyOrder({"type", "foo"})
@Getter
@Setter
public abstract class IntermediateClass extends BaseClass {
}
Child1Class extending IntermediateClass:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@JsonInclude(Include.NON_NULL)
public class Child1Class extends IntermediateClass {
private String foo;
}
Child2Class extending IntermediateClass as well:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@JsonInclude(Include.NON_NULL)
public class Child2Class extends IntermediateClass {
private Integer foo;
}
Child3Class extending BaseClass directly:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@JsonInclude(Include.NON_NULL)
@JsonPropertyOrder({"type", "bar"})
public class Child3Class extends BaseClass {
private String bar;
}
The Spring MVC controller (Spring Boot v2.5.2 application) looks like this:
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController("SSCCEController")
@RequestMapping(value = {"/api/sscce/tests"}, produces = MediaType.APPLICATION_JSON_VALUE)
public class SSCCEController {
@GetMapping(path = "/base")
public BaseClass getBaseTest() {
return new Child1Class();
}
@GetMapping(path = "/intermediate")
public IntermediateClass getIntermediateTest() {
return new Child1Class();
}
}
While for Jackson everything is fine, SpringDoc generates the following OpenAPI Spec:
{
"openapi": "3.0.1",
"info": {
"title": "SSCCE - oneOf inheritance"
},
"servers": [
{
"description": "Generated server url",
"url": "http://localhost"
}
],
"paths": {
"/api/sscce/tests/base": {
"get": {
"operationId": "getBaseTest",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/Child1Class"
},
{
"$ref": "#/components/schemas/Child2Class"
},
{
"$ref": "#/components/schemas/Child3Class"
}
]
}
}
},
"description": "OK"
}
},
"tags": [
"sscce-controller"
]
}
},
"/api/sscce/tests/intermediate": {
"get": {
"operationId": "getIntermediateTest",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/IntermediateClass"
}
}
},
"description": "OK"
}
},
"tags": [
"sscce-controller"
]
}
}
},
"components": {
"schemas": {
"BaseClass": {
"type": "object",
"discriminator": {
"propertyName": "type"
},
"properties": {
"type": {
"type": "string"
}
}
},
"Child1Class": {
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/BaseClass"
},
{
"type": "object",
"properties": {
"foo": {
"type": "string"
}
}
}
]
},
"Child2Class": {
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/BaseClass"
},
{
"type": "object",
"properties": {
"foo": {
"type": "integer",
"format": "int32"
}
}
}
]
},
"Child3Class": {
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/BaseClass"
},
{
"type": "object",
"properties": {
"bar": {
"type": "string"
}
}
}
]
},
"IntermediateClass": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
}
}
}
}
So for /api/sscce/tests/base
the response gets generated as oneOf
as expected.
How can we make it also generate oneOf
for /api/sscce/tests/intermediate
?
Also in the allOf
section of Child1Class
and Child2Class
the IntermediateClass
should be referenced, not BaseClass
! (I tried and added a property to IntermediateClass
but nothing changed.)
Upvotes: 2
Views: 3061
Reputation: 38142
I finally found a solution for this issue: add an @Schema-annotation for the IntermediateClass.
@Schema(
type = "object",
title = "IntermediateClass",
subTypes = {Child1Class.class, Child2Class.class}
)
@JsonPropertyOrder({"type", "foo"})
public abstract class IntermediateClass extends BaseClass {
}
Upvotes: 5