springdoc-openapi icon indicating copy to clipboard operation
springdoc-openapi copied to clipboard

@JsonView ignored for subclasses

Open devopsix opened this issue 3 years ago • 0 comments

I am having a parent class with subclasses. I am using the parent class for @RestController methods' @RequestBody parameters and for return values. And I am using different @JsonView annotations on the classes' fields, the method parameters and return value types.

When generating the OpenAPI Description, the request and response body content schemas contain an incorrect selection of the classes' fields.

public interface Input {}

public interface Output {}

@JsonSubTypes({
    @JsonSubTypes.Type(value = A1.class),
    @JsonSubTypes.Type(value = A2.class),
})
public static abstract class A {
    @JsonView(Input.class)
    public String a_in;
    @JsonView(Output.class)
    public String a_out;
}

@JsonTypeName("A1")
public static class A1 extends A {
    @JsonView(Input.class)
    public String a1_in;
    @JsonView(Output.class)
    public String a1_out;
}

@JsonTypeName("A2")
public static class A2 extends A {
    @JsonView(Input.class)
    public String a2_in;
    @JsonView(Output.class)
    public String a2_out;
}

@PutMapping("/a")
public void createA(@JsonView(Input.class) @RequestBody A a) {
}

@GetMapping("/a/{id}")
public @JsonView(Output.class) A getA(@PathVariable("id") String id) {
    return new A1();
}

This is the actual OpenAPI Description getting generated:

{
  "openapi" : "3.0.1",
  "info" : {
    "title" : "OpenAPI definition",
    "version" : "v0"
  },
  "servers" : [ {
    "url" : "http://localhost:8080",
    "description" : "Generated server url"
  } ],
  "paths" : {
    "/a" : {
      "put" : {
        "tags" : [ "demo-application" ],
        "operationId" : "createA",
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "oneOf" : [ {
                  "$ref" : "#/components/schemas/A1"
                }, {
                  "$ref" : "#/components/schemas/A2"
                } ]
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "200" : {
            "description" : "OK"
          }
        }
      }
    },
    "/a/{id}" : {
      "get" : {
        "tags" : [ "demo-application" ],
        "operationId" : "getA",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "OK",
            "content" : {
              "*/*" : {
                "schema" : {
                  "oneOf" : [ {
                    "$ref" : "#/components/schemas/A1"
                  }, {
                    "$ref" : "#/components/schemas/A2"
                  } ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components" : {
    "schemas" : {
      "A1" : {
        "type" : "object",
        "allOf" : [ {
          "$ref" : "#/components/schemas/A_Input"
        }, {
          "type" : "object",
          "properties" : {
            "a_out" : {
              "type" : "string"
            },
            "a1_in" : {
              "type" : "string"
            },
            "a1_out" : {
              "type" : "string"
            }
          }
        } ]
      },
      "A2" : {
        "type" : "object",
        "allOf" : [ {
          "$ref" : "#/components/schemas/A_Input"
        }, {
          "type" : "object",
          "properties" : {
            "a_out" : {
              "type" : "string"
            },
            "a2_in" : {
              "type" : "string"
            },
            "a2_out" : {
              "type" : "string"
            }
          }
        } ]
      },
      "A_Input" : {
        "type" : "object",
        "properties" : {
          "a_in" : {
            "type" : "string"
          }
        }
      }
    }
  }
}

What I would expect instead:

  • The response body content schema for GET /a/{id} should only include *_out properties.
  • The request body content schema for PUT /a should only include *_in properties.

I.e. something along these lines:

{
  "openapi" : "3.0.1",
  "info" : {
    "title" : "OpenAPI definition",
    "version" : "v0"
  },
  "servers" : [ {
    "url" : "http://localhost:8080",
    "description" : "Generated server url"
  } ],
  "paths" : {
    "/a" : {
      "put" : {
        "tags" : [ "demo-application" ],
        "operationId" : "createA",
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "oneOf" : [ {
                  "$ref" : "#/components/schemas/A1_Input"
                }, {
                  "$ref" : "#/components/schemas/A2_Input"
                } ]
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "200" : {
            "description" : "OK"
          }
        }
      }
    },
    "/a/{id}" : {
      "get" : {
        "tags" : [ "demo-application" ],
        "operationId" : "getA",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "OK",
            "content" : {
              "*/*" : {
                "schema" : {
                  "oneOf" : [ {
                    "$ref" : "#/components/schemas/A1_Output"
                  }, {
                    "$ref" : "#/components/schemas/A2_Output"
                  } ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components" : {
    "schemas" : {
      "A1_Input" : {
        "type" : "object",
        "allOf" : [ {
          "$ref" : "#/components/schemas/A_Input"
        }, {
          "type" : "object",
          "properties" : {
            "a1_in" : {
              "type" : "string"
            }
          }
        } ]
      },
      "A2_Input" : {
        "type" : "object",
        "allOf" : [ {
          "$ref" : "#/components/schemas/A_Input"
        }, {
          "type" : "object",
          "properties" : {
            "a2_in" : {
              "type" : "string"
            }
          }
        } ]
      },
      "A_Input" : {
        "type" : "object",
        "properties" : {
          "a_in" : {
            "type" : "string"
          }
        }
      },
      "A1_Output" : {
        "type" : "object",
        "allOf" : [ {
          "$ref" : "#/components/schemas/A_Output"
        }, {
          "type" : "object",
          "properties" : {
            "a1_out" : {
              "type" : "string"
            }
          }
        } ]
      },
      "A2_Output" : {
        "type" : "object",
        "allOf" : [ {
          "$ref" : "#/components/schemas/A_Output"
        }, {
          "type" : "object",
          "properties" : {
            "a2_out" : {
              "type" : "string"
            }
          }
        } ]
      },
      "A_Output" : {
        "type" : "object",
        "properties" : {
          "a_out" : {
            "type" : "string"
          }
        }
      }
    }
  }
}

I have tried adding @Schema(subTypes = {A1.class, A2.class}) to A but that did not help either.

I have pushed a minimal reproducible example to GitHub.

Spring Boot version: 2.7.2 springdoc-openapi version: 1.6.9 springdoc-openapi modules: common, ui, webmvc-core Java, OpenJDK 15.0.1

devopsix avatar Aug 11 '22 09:08 devopsix