Using Java annotations, is there a way to specify Swagger/OpenAPI schemas inside other schemas?

Viewed 11205

I'm trying to use Swagger UI to create and deploy my documentation along with the API I've written using Spring Boot. I know Swagger provides some annotations to assist with writing documentation in the actual controller classes, but I'm having trouble trying to get them to do what I need.

My problem is that I have a generic DTO class that gets returned by every call to my API. This DTO has a contents field for generic objects. If I were using the objects directly, I know I could use something like

@ApiResponse(responseCode = "200", description = "Customer found",
    content = @Content(
        schema = @Schema(implementation = Customer.class)))

in order to give a specification of what the JSON representation of the object should look like. However, because I'm wrapping everything in a specific ResponseDTO class, I need a way to specify what the contents field should look like, and I'm not sure which annotations I can use to accomplish this. I feel like there should definitely be something like

@ApiResponse(responseCode = "200", description = "Customer found",
    content = @Content(
        schema = @Schema(implementation = DTO.class, 
                         fields = { "contents" = @Schema(implementation = Customer.class)})))

or something similar. I haven't been able to find an explanation of how to really accomplish this. My intuition suggests there should be a way to put a schema inside a schema, but maybe there's another solution I haven't considered. Any help or direction in which to look would be hugely appreciated. Thanks in advance.

3 Answers

I'm aware this post is old, but I'm posting this anyway in case anyone has a similar problem.

I also had to return a list of objects where one of the fields of these objects has to change according to some request parameters. In order to document these subschemas (schema inside schema as you said) in Swagger, you can use the oneOf attribute of @Schema annotation and provide a list of your generic classes.

Here is an example:

Customer.java:

public class Customer {

  // Some other fields...

  @Schema(
      description = "List of generic contents inside Customer",
      oneOf = {Bar.class, Foo.class})
  private List<IContent> contents;

}

IContent.java is just some interface (or it can be any super class of generic classes):

public interface IContent extends Serializable { }

Let's say Foo.java and Bar.java is two of your generic classes:

public class Bar implements IContent {

  @Schema(description = "Some dummy name field inside Bar")
  private String dummyName;

  @Schema(description = "Some dummy number field inside Bar")
  private Integer dummyNo;

}

public class Foo implements IContent {

  @Schema(description = "Some dummy ID field inside Foo")
  private Long contentID;

}

The resulting Swagger UI: swagger-ui

Please also see allOf and anyOf attributes for other types of subschema attributes in case you have different needs than documenting a list of generic objects.

I successfully mark the field of class with @Schema and it works fine but I use to give the information for map.

class CreateReq extends DTO {

    @Schema(description="",
        implementation = Products.class)
    Map<String,Map<String,Object>>  products;

}

    // For openapi document only
    private static class Products {
        ProductFields productId1;
        ProductFields productId2;
    }

    // For openapi document only
    private static class ProductFields {
        String field1;
        Object field2;

    }

It looks like you're doing this backwards. I do this by creating a .yaml file that specifies the API, using the editor at http://swagger.io

That way, your .yaml file can specify a different DTO for each data type, and you can specify for each API what type it returns.

Also, you don't post what your ResponseDTO class looks like. I hope you've generified it:

public class ResponseDTO<T> {
  public T getData() { … }
  …
}

But I'm not sure it's a good idea to even have your own object like this. On some of my past projects, we've defined a separate response class for each data type, but they all do the same thing! There's probably a class like this already written for you by your framework.

You're using Spring Boot, which is what I use. For Spring, the class is org.springframework.http.ResponseEntity. (Other frameworks probably have their own version of this.) And it's generified, so you can add content of any data type to it. But Swagger asks you to specify the type of the added content, not the ResponseEntity class itself. Your ResponseDTO class is getting in your way.

So if you want to return a type of CustomProduct, your swagger-generated annotations will specify CustomProduct.class, and your service method will look something like this:

… (more annotations)
@ApiResponses(value = { 
  @ApiResponse(code=200, message="Found", response=CustomProduct.class)
})
public ResonseEntity<CustomProduct> getCustomProduct(…) {
  CustomProduct myCustomProduct = retrieveCustomProduct(…);
  return new ResponseEntity<>(myCustomProduct, HTTPStatus.OK);
}
Related