'SpringDoc for a complex response type

I am documenting an existing Java based API using SpringDoc. There is a common response object for all of our APIs (shown below) which has two fields, a status and an object containing the actual result.

class MyResponse {
    private String status;
    private Object result; 
.........
}

Is there a way to allow me to document the actual runtime type of the result object depending on the API that is called? e.g. if my call is to the getCustomer() API, I wish to generate documentation for a Customer result object, and if my call is to the getProduct() API, I wish to generate documentation for a Product result object.



Solution 1:[1]

You should use the @ApiResponse to document the type of data that will be returned. Following are the annotations of your interest -

  • @ApiResponses - Represents the array of responses a method might return, including error responses. The common set of responses can be specified at the class level while the specific once can be provided at the method level.

    • value - An array of @ApiResponse.
  • @ApiResponse - Used to describe a specific response that a method might return. Can not be used at the class level.

    • responseCode - A String value representing the response code, such as "200", "401" etc.
    • description - Description of the response. Usually the HTTP Status Message, such as "OK", "Unauthorized" etc.
    • content - Describes the content that will be returned by the method. Refer the @Content annotation for details.

    Important - Responses such as 401 - Unauthorized may not return anything. In such cases, content should be initialed as an empty @Schema as shown below.

    @ApiResponse(responseCode = "401", description = "Unauthorized", content = {@Content(schema = @Schema())})
    
  • @Content - Describes the content that will be returned as a response by the method.

    • mediaType - Specifies the type of object that will be returned by the API. Mandatory to be specified, if a valid response is returned. Can be defined at the class level. Examples application/json or text/plain etc.
    • schema - The @Schema object that will be returned as a response from the method. Refer @Schema for more details.
  • @Schema - Describes the schema object (POJO or can even be a primitive datatype, set the mediaType accordingly) that will be returned as a response. Not specifying any attributes means nothing will be returned from the object (often used with error responses).

    • implementation - Name of the class object that will be returned as a response from the method. Defaults to Void.class
  • @Parameter - Used at the method parameters with other annotations such as @RequestParam, @PathVariable etc.

    • description - Describes the parameter that is expected.
    • required - Boolean value specifying if the parameter is optional or mandatory. Defaults to the one specified by the parameter such as @RequestParam, @PathVariable etc.
  • @io.swagger.v3.oas.annotations.parameters.RequestBody - Describes the request body expected by the method handling the request.

    • description - Provides a description for the Request Body.
    • required - Boolean value specifying if the body is optional or mandatory. Defaults to true.

    Remember

    • Although @Parameter can also be used instead of this, in that case, all the class obejcts are resolved by reference, thus only the one described last is retained.
    • This @RequestBody is different from the one provided by the Spring, and thus must be used along with the @org.springframework.web.bind.annotation.RequestBody

Below is an example of a controller with the required annotations for documentation.

@RestController

// the "produces" attribute in @RequestMapping can be used to specify the default mediaType.
@RequestMapping(path = "/api/v1/user/", produces = { MediaType.APPLICATION_JSON_VALUE })

// Defines common tag for all the operartions handled by this class
@Tag(name = "User Operations", description = "APIs for operation on User")

// API responses that might be returned by all the methods in this class
@ApiResponses(value = {
        @ApiResponse(responseCode = "400", description = "Bad Request", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = UserErrorResponse.class))}),
        @ApiResponse(responseCode = "500", description = "Internal Server Error", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = UserErrorResponse.class))})
})
public class UserController {

    // POST Method

    // Swagger Annotations
    @Operation(summary = "Create User", description = "User-ID is generated and maintained by the service.", tags = {"User Operations"})
    @ApiResponses(value = {
            @ApiResponse(responseCode = "201", description = "Created", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = UserCreateResponse.class))}),
            @ApiResponse(responseCode = "409", description = "User Collision Detected", content = {@Content(schema = @Schema())})
    })

    // Spring Annotations
    @ResponseStatus(code = HttpStatus.CREATED)
    @PostMapping(value = "/patients", consumes = { MediaType.APPLICATION_JSON_VALUE })
    public ResponseEntity<MyResponse> createUser(
        // Note the two @RequestBody from Swagger and Spring
        @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A request to create user object") @Valid @RequestBody final User user)
    {
        ...
    }


    // GET Method

    // Swagger Annotations
    @ApiResponses(value = {
            @ApiResponse(
                responseCode = "200", description = "OK", 
                // Note the way content is defined for valid objects
                content = {@Content(mediaType = "application/json", schema = @Schema(implementation = User.class))})),

            @ApiResponse(
                responseCode = "404", description = "Not Found",
                // Note the way, when no object is returned
                content = {@Content(schema = @Schema())})),
    })

    // Spring Annotations
    @ResponseStatus(HttpStatus.OK)
    @GetMapping(value = "")
    public MyResponse getUser(
            @Parameter(required = false, description = "Search by firstName") @RequestParam(required = false) String firstName) 
    {
        ...
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Debargha Roy