'How to provide completely custom JSON for example in Swagger?

I have Java endpoint which receives json-deserializable object. Unfortunately, Swagger is unable to auto-generate good example for it. Is it possible to provide completely custom JSON for an example?


Example is below, regard class Body. It has two fields.

One field is a Set. I want to provide some example list of values for it. I can't use example parameter for this.

Another field is a Parent. It can contain one of two of subclessed, Child1 and Child2. Springfox generates me

{
  "parent": {
    "@child#": "string"
  },
  "tags": "[\"tag1\", \"tag2\"]"
}

and I can't send this value (it's incorrect serialization). While I want to have

{
  "parent": {
    "@child#": "1",
    "field1": "value of field 1"
  },
  "tags": ["tag1", "tag2"]
}

The code:

package com.example.demo;

import java.io.IOException;
import java.util.Set;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DatabindContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

@RestController
@SpringBootApplication
@Configuration
@EnableOpenApi
public class DemoApplication {

    @PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE)
    public Body create(@RequestBody Body body) {
        return body;
    }

    @Bean
    public Docket docket() {
        return new Docket(DocumentationType.OAS_30)
            .select()
            .apis(RequestHandlerSelectors.basePackage(DemoApplication.class.getPackageName()))
            .paths(PathSelectors.any())
            .build()
            //.apiInfo(apiInfo())
            //.securitySchemes(Collections.singletonList(apiKey()))
            //.protocols(getProtocols(systemSettings))
            ;
    }

    public static class Body {

        @ApiModelProperty(example = "[\"tag1\", \"tag2\"]")
        public Set<String> tags;

        public Parent parent;

    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "@child#", include = JsonTypeInfo.As.EXISTING_PROPERTY, visible = true)
    @JsonTypeIdResolver(MyTypeIdResolver.class)
    @ApiModel(discriminator = "@child#")
    public static class Parent {

        final String childTypeNumber;

        @JsonProperty("@child#")
        public String childTypeNumber() {
            return childTypeNumber;
        }

        public Parent(String childTypeNumber) {
            this.childTypeNumber = childTypeNumber;
        }
    }

    public static class MyTypeIdResolver extends TypeIdResolverBase {
        private JavaType superType;

        @Override
        public void init(JavaType baseType) {
            superType = baseType;
        }

        @Override
        public String idFromValue(Object value) {
            return null;
        }

        @Override
        public String idFromValueAndType(Object value, Class<?> suggestedType) {
            return null;
        }

        @Override
        public JsonTypeInfo.Id getMechanism() {
            return null;
        }

        @Override
        public JavaType typeFromId(DatabindContext context, String id) throws IOException {
            char c = id.charAt(0);
            Class<?> subType = null;
            switch (c) {
            case '1':
                subType = Child1.class;
                break;
            case '2':
                subType = Child2.class;
                break;
            default:
                throw new RuntimeException("Invalid Child type");
            }
            return context.constructSpecializedType(superType, subType);
        }
    }

    public static class Child1 extends Parent {

        public String field1;

        public Child1() {
            super("1");
        }
    }

    public static class Child2 extends Parent {

        public String field2;

        public Child2() {
            super("2");
        }
    }



    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}


Solution 1:[1]

From what I understand, you want swagger to display the resource returned by the endpoint. If so, this is the solution:

@Operation(summary = "create new resource", 
               description = "create resourcey completely", responses = {
            @ApiResponse(responseCode = "200",
                    description = "createresource",
                    content = {@Content(mediaType = "application/json",
                    schema = @Schema(implementation = Body.class))})
@PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE)
    public Body create(@RequestBody Body body) {
        return body;
    }

So that the controller does not have so many things left, what is done is to create the controller interface with all the annotations on the method signature, then your controller will implement the interface that already has all the documentation annotations.

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 Joel