SpringDoc + JsonSubTypes:没有为中间 class 生成模式

SpringDoc + JsonSubTypes: no oneOf schema generated for intermediate class

我们有一个 class 层次结构,如下所示:

基类:

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();

}

中间类扩展基类:

import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.Getter;
import lombok.Setter;

@JsonPropertyOrder({"type", "foo"})
@Getter
@Setter
public abstract class IntermediateClass extends BaseClass {

}

Child1Class 扩展 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 也扩展了 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 Child2Class extends IntermediateClass {

    private Integer foo;

}

直接扩展 BaseClass 的 Child3Class:

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;

}

Spring MVC 控制器(Spring Boot v2.5.2 应用程序)如下所示:

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();
    }

}

虽然 Jackson 一切正常,SpringDoc 生成了以下 OpenAPI 规范:

{
  "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"
          }
        }
      }
    }
  }
}

因此,对于 /api/sscce/tests/base,响应将按预期生成为 oneOf

我们如何让它也为 /api/sscce/tests/intermediate 生成 oneOf

同样在 Child1ClassChild2ClassallOf 部分中,应该引用 IntermediateClass,而不是 BaseClass! (我试着在 IntermediateClass 中添加了一个 属性 但没有任何改变。)

我终于找到了这个问题的解决方案:为 IntermediateClass 添加一个@Schema-annotation。

@Schema(
        type = "object",
        title = "IntermediateClass",
        subTypes = {Child1Class.class, Child2Class.class}
)
@JsonPropertyOrder({"type", "foo"})
public abstract class IntermediateClass extends BaseClass {

}