解决Swagger2返回map复杂结构不能解析的问题

  • Post category:http
  1. 背景介绍

Swagger2是一种流行的RESTful API文档管理工具,可以以动态的方式展现API的功能和数据输出。但是,在Swagger2中,对于返回类型为Map的复杂结构,可能会出现无法展示的问题。例如,在Swagger2的UI页面中,一个返回Map对象的API会变成一个空白的Response Body。

  1. 解决方案

为了解决Swagger2无法支持复杂结构的问题,我们需要对Swagger2进行配置和扩展,具体步骤如下:

第一步:添加Swagger2的依赖和配置文件

在pom.xml文件中添加以下依赖:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>3.0.0</version>
</dependency>

在application.properties或application.yml中添加以下Swagger2配置:

spring:
  application:
    name: My Application
  swagger:
    enabled: true
  resources:
    add-mappings: true
  profiles:
    active: dev

swagger:
  swagger-ui:
    url: /v3/api-docs.yaml

第二步:扩展Swagger2的Parser和Serializer

Swagger2默认使用Jackson库进行JSON对象的解析和序列化,但是Jackson库对于复杂的Map结构无法进行完整的解析和序列化。因此,我们需要扩展Swagger2的Parser和Serializer以支持复杂Map结构的解析和序列化。

以下是一个扩展Jackson库的示例代码:

@Component
public class CustomJsonSerializer extends JsonSerializer<Map<?, ?>> {

    @Override
    public void serialize(Map<?, ?> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (value == null) {
            gen.writeNull();
            return;
        }
        gen.writeStartObject();
        for (Map.Entry<?, ?> entry : value.entrySet()) {
            Object key = entry.getKey();
            Object val = entry.getValue();
            if (key instanceof String) {
                gen.writeStringField(key.toString(), val.toString());
            }
        }
        gen.writeEndObject();
    }

}

这个示例将Map转变为一个JSON对象,其中每个Map的key-value对都被转变为一个JSON串,以防止Jackson库无法解析复杂的Map结构。

第三步:添加Swagger2的注解

为了让Swagger2识别自定义注解,我们需要在Swagger2的配置类中添加以下内容:

import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class Swagger2Configurer {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example"))
                .paths(PathSelectors.any())
                .build()
                .apiInfo(apiInfo());
    }

    private ApiInfo apiInfo() {
        return new ApiInfo(
                "My REST API",
                "Some custom description of API.",
                "API TOS",
                "Terms of service",
                new Contact("John Doe", "www.example.com", "myeaddress@company.com"),
                "License Info",
                "API License URL",
                Collections.emptyList()
        );
    }

    @Bean
    public Jackson2ObjectMapperBuilder jacksonBuilder(
            ApplicationContext context) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.applicationContext(context);
        builder.serializerByType(Object.class, new CustomJsonSerializer());
        builder.deserializerByType(Object.class, new CustomJsonDeserializer());
        return builder;
    }
}

这个配置类中添加了一个自定义Jackson的Serializer和Deserializer,用于解析和序列化返回类型为Map的复杂结构,并且包含一些Swagger2的通用配置。

  1. 示例说明

以下是两个示例,用于说明如何解决Swagger2返回Map类型的复杂结构无法解析的问题。

示例1:使用自定义的Swagger2配置类和注解

@RestController
@Api(value = "MyController", description = "Some custom description of MyController.")
public class MyController {

    @ApiOperation(value = "Get user by id.", notes = "More notes about this method.")
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Success", response = User.class)
    })
    @GetMapping("/user")
    public Map<String, User> getUser() {
        Map<String, User> userMap = new HashMap<>();
        User user = new User();
        user.setId(1234L);
        user.setName("John Doe");
        user.setEmail("johndoe@example.com");
        userMap.put("user", user);
        return userMap;
    }

}

示例2:使用自定义的Serializer和Deserializer

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private Long id;
    private String name;
    private String email;

}

public class CustomJsonDeserializer extends JsonDeserializer<Map<?, ?>> {

    @Override
    public Map<?, ?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        TreeNode treeNode = p.getCodec().readTree(p);

        Map<Object, Object> map = new HashMap<>();

        if (treeNode.isArray()) {
            ArrayNode arrayNode = (ArrayNode) treeNode;
            for (JsonNode node : arrayNode) {
                if (node.isArray()) {
                    map.put(null, parse(node));
                } else if (node.isObject()) {
                    map.put(null, parse(node));
                } else {
                    throw new IllegalArgumentException("Array must contain only objects");
                }
            }
        } else if (treeNode.isObject()) {
            ObjectNode objectNode = (ObjectNode) treeNode;
            Iterator<String> fieldNames = objectNode.fieldNames();

            while (fieldNames.hasNext()) {
                String fieldName = fieldNames.next();
                JsonNode jsonNode = objectNode.get(fieldName);

                if (jsonNode.isValueNode()) {
                    map.put(fieldName, jsonNode.asText());
                } else if (jsonNode.isArray()) {
                    map.put(fieldName, parse(jsonNode));
                } else if (jsonNode.isObject()) {
                    map.put(fieldName, parse(jsonNode));
                } else {
                    throw new IllegalArgumentException("Object property must be value, array or object");
                }
            }
        }
        return map;
    }

    private Map<Object, Object> parse(JsonNode node) throws IOException {
        return p.readValueAs(Map.class);
    }
}

这两个示例展示了如何扩展Swagger2来解决返回类型为Map的复杂结构无法解析的问题。第一个示例使用了自定义的Swagger2配置类和注解,而第二个示例则使用了自定义Serializer和Deserializer来解析和序列化复杂的Map结构。