了解并配置 Spring JSON 字符串响应的编组配置
Understand and configure Spring JSON marshalling configuration of String responses
对于 Spring MVC 项目(不是 Spring 引导),我正在配置 JSON 转换器以自定义所有 REST 端点的 JSON 响应,即删除 null
字段并设置日期格式。在将 SpringDoc 引入项目后,我不得不添加一个 StringHttpMessageConverter
以防止生成的 OpenAPI JSON 被 return 编辑为字符串。
没有 StringHttpMessageConverter
OpenAPI JSON 看起来像这样:
"{\"openapi\":\"3.0.1\",\"info\":{\"title\":\"OpenAPI definition\",\"version\":\"v0\"},\"servers\":[{\"url\":\"http://localhost:8080\",\"description\":\"Generated server url\"}],\"paths\":{\"/get\":{\"get\":{\"tags\":[\"controller\"],\"operationId\":\"getSomeMap\",\"responses\":{\"200\":{\"description\":\"default response\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/ImmutableMultimapStringString\"}}}}}}}},\"components\":{\"schemas\":{\"ImmutableMultimapStringString\":{\"type\":\"object\",\"properties\":{\"empty\":{\"type\":\"boolean\"}}}}}}"
使用 StringHttpMessageConverter
看起来像这样,这是想要的结果:
{"openapi":"3.0.1","info":{"title":"OpenAPI definition","version":"v0"},"servers":[{"url":"http://localhost:8080","description":"Generated server url"}],"paths":{"/get":{"get":{"tags":["controller"],"operationId":"getSomeMap","responses":{"200":{"description":"default response","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ImmutableMultimapStringString"}}}}}}}},"components":{"schemas":{"ImmutableMultimapStringString":{"type":"object","properties":{"empty":{"type":"boolean"}}}}}}
然而,这确实会导致几个端点出现问题,这些端点 return 一个字符串作为它们的响应。他们应该 return 一个有效的 JSON 字符串:"response-string"
但他们 return 字符串作为纯文本:response-string
,省略双引号,使其无效 JSON.
我怎样才能保持当前配置的完整性,以便 SpringDoc OpenAPI JSON 正确 returned 同时还具有具有字符串响应的端点 return a有效 JSON 字符串?
使用的配置:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
WebContentInterceptor webContentInterceptor = new WebContentInterceptor();
webContentInterceptor.setCacheSeconds(0);
webContentInterceptor.setUseExpiresHeader(true);
webContentInterceptor.setUseCacheControlHeader(true);
webContentInterceptor.setUseCacheControlNoStore(true);
registry.addInterceptor(webContentInterceptor);
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json", MediaType.APPLICATION_JSON);
mediaTypes.put("xml", MediaType.APPLICATION_XML);
configurer.favorPathExtension(false);
configurer.favorParameter(true);
configurer.mediaTypes(mediaTypes);
configurer.defaultContentType(MediaType.APPLICATION_JSON_UTF8);
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// Note that the order matters here! If the StringHttpMessageConverter is add after the jsonConverter
// the documentation JSON is returned as a giant string instead of a (valid) JSON object
converters.add(new StringHttpMessageConverter());
converters.add(jsonConverter());
}
@Bean
public MappingJackson2HttpMessageConverter jsonConverter() {
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.timeZone(TimeZone.getTimeZone(timeZone));
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(
builder.build()
);
jsonConverter.setSupportedMediaTypes(supportedMediaTypes);
return jsonConverter;
}
@Bean
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setClassesToBeBound(KioskProfiel.class, KioskProfielRegel.class, TitlesetTO.class, TitlesetTitel.class);
return jaxb2Marshaller;
}
@Bean
public MarshallingHttpMessageConverter marshallingConverter() {
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_XML);
MarshallingHttpMessageConverter marshallingConverter = new MarshallingHttpMessageConverter(jaxb2Marshaller());
marshallingConverter.setSupportedMediaTypes(supportedMediaTypes);
return marshallingConverter;
}
}
编辑
我已经尝试覆盖 OpenApiResource
将端点的 produces
值设置为 TEXT_PLAIN_VALUE
和 application/json
但问题仍然存在。不允许尝试将 return 类型从 String
更改为 TextNode
,因此这似乎不是一个选项。
或者,我尝试通过注册 Filter
来更正格式错误的响应来解决此问题,但那是行不通的。
也许我仍然遗漏了一些东西,但我别无选择。使用我当前的项目配置,在使用自定义 MappingJackson2HttpMessageConverter
时,我无法将 SpringDoc 转换为 return 有效的 OpenAPI JSON。现在,我将坚持使用 Swagger 2.0,并将研究替代库以迁移到 OpenAPI 3.0。
终于找到了可行的解决方案!它由两部分组成。首先是配置转换器。简而言之,我们注册默认转换器,然后删除默认 JSON 转换器,MappingJackson2HttpMessageConverter
并将我们的自定义 JSON 转换器添加为转换器列表中的第一个转换器。重要的是,自定义 JSON 转换器位于 StringHttpMessageConverter
else 端点之前的转换器列表中 return JSON 具有 String
作为其 Java return 输入 return 不带双引号的字符串使其无效 JSON.
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
converters.add(0, jsonConverter());
}
@Bean
public MappingJackson2HttpMessageConverter jsonConverter() {
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.timeZone(TimeZone.getTimeZone(timeZone));
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(
builder.build()
);
jsonConverter.setSupportedMediaTypes(supportedMediaTypes);
return jsonConverter;
}
}
其次,这导致 OpenAPI JSON 成为一个大的(转义的)字符串,如问题中所述。为了解决这个问题,我们覆盖了 OpenApiWebMvcResource
class 中的 openapiJson
方法(和端点),默认情况下使用 return OpenAPI JSON,以生成 text/plain
而不是 application/json
。这样,文档 JSON 就不再 return 编辑为(转义的)字符串。
@RestController
public class OpenApiResource extends OpenApiWebMvcResource {
@Override
@Operation(hidden = true)
@GetMapping(value = Constants.API_DOCS_URL, produces = MediaType.TEXT_PLAIN_VALUE)
public String openapiJson(
HttpServletRequest request,
@Value(Constants.API_DOCS_URL) String apiDocsUrl
)
throws JsonProcessingException {
calculateServerUrl(request, apiDocsUrl);
OpenAPI openAPI = this.getOpenApi();
return Json.mapper().writeValueAsString(openAPI);
}
}
请注意,为简洁起见,在上面的两个示例 class 中仅列出了相关方法。
另一个快速解决方法是配置 swagger-ui 以使用 YAML 版本的 OpenAPI 文档(而不是 JSON),只需将此 属性 添加到您的 application.yaml :
springdoc.swagger-ui.url: /v3/api-docs.yaml
对于 Spring MVC 项目(不是 Spring 引导),我正在配置 JSON 转换器以自定义所有 REST 端点的 JSON 响应,即删除 null
字段并设置日期格式。在将 SpringDoc 引入项目后,我不得不添加一个 StringHttpMessageConverter
以防止生成的 OpenAPI JSON 被 return 编辑为字符串。
没有 StringHttpMessageConverter
OpenAPI JSON 看起来像这样:
"{\"openapi\":\"3.0.1\",\"info\":{\"title\":\"OpenAPI definition\",\"version\":\"v0\"},\"servers\":[{\"url\":\"http://localhost:8080\",\"description\":\"Generated server url\"}],\"paths\":{\"/get\":{\"get\":{\"tags\":[\"controller\"],\"operationId\":\"getSomeMap\",\"responses\":{\"200\":{\"description\":\"default response\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/ImmutableMultimapStringString\"}}}}}}}},\"components\":{\"schemas\":{\"ImmutableMultimapStringString\":{\"type\":\"object\",\"properties\":{\"empty\":{\"type\":\"boolean\"}}}}}}"
使用 StringHttpMessageConverter
看起来像这样,这是想要的结果:
{"openapi":"3.0.1","info":{"title":"OpenAPI definition","version":"v0"},"servers":[{"url":"http://localhost:8080","description":"Generated server url"}],"paths":{"/get":{"get":{"tags":["controller"],"operationId":"getSomeMap","responses":{"200":{"description":"default response","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ImmutableMultimapStringString"}}}}}}}},"components":{"schemas":{"ImmutableMultimapStringString":{"type":"object","properties":{"empty":{"type":"boolean"}}}}}}
然而,这确实会导致几个端点出现问题,这些端点 return 一个字符串作为它们的响应。他们应该 return 一个有效的 JSON 字符串:"response-string"
但他们 return 字符串作为纯文本:response-string
,省略双引号,使其无效 JSON.
我怎样才能保持当前配置的完整性,以便 SpringDoc OpenAPI JSON 正确 returned 同时还具有具有字符串响应的端点 return a有效 JSON 字符串?
使用的配置:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
WebContentInterceptor webContentInterceptor = new WebContentInterceptor();
webContentInterceptor.setCacheSeconds(0);
webContentInterceptor.setUseExpiresHeader(true);
webContentInterceptor.setUseCacheControlHeader(true);
webContentInterceptor.setUseCacheControlNoStore(true);
registry.addInterceptor(webContentInterceptor);
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json", MediaType.APPLICATION_JSON);
mediaTypes.put("xml", MediaType.APPLICATION_XML);
configurer.favorPathExtension(false);
configurer.favorParameter(true);
configurer.mediaTypes(mediaTypes);
configurer.defaultContentType(MediaType.APPLICATION_JSON_UTF8);
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// Note that the order matters here! If the StringHttpMessageConverter is add after the jsonConverter
// the documentation JSON is returned as a giant string instead of a (valid) JSON object
converters.add(new StringHttpMessageConverter());
converters.add(jsonConverter());
}
@Bean
public MappingJackson2HttpMessageConverter jsonConverter() {
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.timeZone(TimeZone.getTimeZone(timeZone));
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(
builder.build()
);
jsonConverter.setSupportedMediaTypes(supportedMediaTypes);
return jsonConverter;
}
@Bean
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setClassesToBeBound(KioskProfiel.class, KioskProfielRegel.class, TitlesetTO.class, TitlesetTitel.class);
return jaxb2Marshaller;
}
@Bean
public MarshallingHttpMessageConverter marshallingConverter() {
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_XML);
MarshallingHttpMessageConverter marshallingConverter = new MarshallingHttpMessageConverter(jaxb2Marshaller());
marshallingConverter.setSupportedMediaTypes(supportedMediaTypes);
return marshallingConverter;
}
}
编辑
我已经尝试覆盖 OpenApiResource
将端点的 produces
值设置为 TEXT_PLAIN_VALUE
和 application/json
但问题仍然存在。不允许尝试将 return 类型从 String
更改为 TextNode
,因此这似乎不是一个选项。
或者,我尝试通过注册 Filter
来更正格式错误的响应来解决此问题,但那是行不通的。
也许我仍然遗漏了一些东西,但我别无选择。使用我当前的项目配置,在使用自定义 MappingJackson2HttpMessageConverter
时,我无法将 SpringDoc 转换为 return 有效的 OpenAPI JSON。现在,我将坚持使用 Swagger 2.0,并将研究替代库以迁移到 OpenAPI 3.0。
终于找到了可行的解决方案!它由两部分组成。首先是配置转换器。简而言之,我们注册默认转换器,然后删除默认 JSON 转换器,MappingJackson2HttpMessageConverter
并将我们的自定义 JSON 转换器添加为转换器列表中的第一个转换器。重要的是,自定义 JSON 转换器位于 StringHttpMessageConverter
else 端点之前的转换器列表中 return JSON 具有 String
作为其 Java return 输入 return 不带双引号的字符串使其无效 JSON.
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
converters.add(0, jsonConverter());
}
@Bean
public MappingJackson2HttpMessageConverter jsonConverter() {
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.timeZone(TimeZone.getTimeZone(timeZone));
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(
builder.build()
);
jsonConverter.setSupportedMediaTypes(supportedMediaTypes);
return jsonConverter;
}
}
其次,这导致 OpenAPI JSON 成为一个大的(转义的)字符串,如问题中所述。为了解决这个问题,我们覆盖了 OpenApiWebMvcResource
class 中的 openapiJson
方法(和端点),默认情况下使用 return OpenAPI JSON,以生成 text/plain
而不是 application/json
。这样,文档 JSON 就不再 return 编辑为(转义的)字符串。
@RestController
public class OpenApiResource extends OpenApiWebMvcResource {
@Override
@Operation(hidden = true)
@GetMapping(value = Constants.API_DOCS_URL, produces = MediaType.TEXT_PLAIN_VALUE)
public String openapiJson(
HttpServletRequest request,
@Value(Constants.API_DOCS_URL) String apiDocsUrl
)
throws JsonProcessingException {
calculateServerUrl(request, apiDocsUrl);
OpenAPI openAPI = this.getOpenApi();
return Json.mapper().writeValueAsString(openAPI);
}
}
请注意,为简洁起见,在上面的两个示例 class 中仅列出了相关方法。
另一个快速解决方法是配置 swagger-ui 以使用 YAML 版本的 OpenAPI 文档(而不是 JSON),只需将此 属性 添加到您的 application.yaml :
springdoc.swagger-ui.url: /v3/api-docs.yaml