如何在Spring MVC中基于http请求头启用json的动态漂亮打印?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在Spring MVC中基于http请求头启用json的动态漂亮打印?相关的知识,希望对你有一定的参考价值。

我想基于http参数动态地从Spring MVC Restcontrollers打印json响应(如此处建议:http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api#pretty-print-gzip)。

我已经找到了通过静态配置进行漂亮打印的配置,但不是如何动态地进行打印?

When using Spring MVC for REST, how do you enable Jackson to pretty-print rendered JSON?

知道怎么做吗?

答案

Introducing A New Media Type


您可以定义一个新的媒体类型,例如application/pretty+json并注册一个转换为该媒体类型的新HttpMessageConverter。事实上,如果客户端使用Accept: application/pretty+json标头发送请求,我们的新HttpMessageConverter将写入响应,否则,普通的旧MappingJackson2HttpMessageConverter会这样做。

所以,扩展MappingJackson2HttpMessageConverter如下:

public class PrettyPrintJsonConverter extends MappingJackson2HttpMessageConverter {
    public PrettyPrintJsonConverter() {
        setPrettyPrint(true);
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return Collections.singletonList(new MediaType("application", "pretty+json"));
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        boolean canWrite = super.canWrite(clazz, mediaType);
        boolean canWritePrettily = mediaType != null && 
                                   mediaType.getSubtype().equals("pretty+json");

        return canWrite && canWritePrettily;
    }
}

构造函数中的setPrettyPrint(true)将为我们提供帮助。然后我们应该注册这个HttpMessageConverter

@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new PrettyPrintJsonConverter());
    }
}

正如我所说的,如果客户端使用application/pretty+json Accept标头发送请求,我们的PrettyPrintJsonConverter将会正常写入JSON表示。否则,MappingJackson2HttpMessageConverter会将一个紧凑的JSON写入响应主体。

你可以用ResponseBodyAdvice甚至拦截器实现同样的目标,但在我看来,注册一个全新的HttpMessageConverter是更好的方法。

另一答案

要使用?pretty = true参数切换到漂亮的渲染,我使用自定义MappingJackson2HttpMessageConverter

@Configuration
@RestController
public class MyController {

@Bean
MappingJackson2HttpMessageConverter currentMappingJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter jsonConverter = new CustomMappingJackson2HttpMessageConverter();
        return jsonConverter;
}


public static class Input {
    public String pretty;
}

public static class Output {
    @JsonIgnore
    public String pretty;
}

@RequestMapping(path = "/api/test", method = {RequestMethod.GET, RequestMethod.POST})
Output test( @RequestBody(required = false) Input input,
             @RequestParam(required = false, value = "pretty") String pretty)
{
     if (input.pretty==null) input.pretty = pretty;
     Output output = new Output();
     output.pretty = input.pretty;
     return output;
}
}

转换器:

public class CustomMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {

    ObjectMapper objectMapper;

    ObjectMapper prettyPrintObjectMapper;

    public CustomMappingJackson2HttpMessageConverter() {
        objectMapper = new ObjectMapper();
        prettyPrintObjectMapper = new ObjectMapper();
        prettyPrintObjectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);

    }


    @Override
    @SuppressWarnings("deprecation")
    protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {

        JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
        JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
        try {
            writePrefix(generator, object);

            Class<?> serializationView = null;
            FilterProvider filters = null;
            Object value = object;
            JavaType javaType = null;
            if (object instanceof MappingJacksonValue) {
                MappingJacksonValue container = (MappingJacksonValue) object;
                value = container.getValue();
                serializationView = container.getSerializationView();
                filters = container.getFilters();
            }
            javaType = getJavaType(type, null);

            ObjectMapper currentMapper = objectMapper;
            Field prettyField = ReflectionUtils.findField(object.getClass(), "pretty");
            if (prettyField != null) {
                Object prettyObject = ReflectionUtils.getField(prettyField, object);
                if (prettyObject != null  &&  prettyObject instanceof String) {
                    String pretty = (String)prettyObject;
                    if (pretty.equals("true"))
                        currentMapper = prettyPrintObjectMapper;
                }
            }

            ObjectWriter objectWriter;
            if (serializationView != null) {
                objectWriter = currentMapper.writerWithView(serializationView);
            }
            else if (filters != null) {
                objectWriter = currentMapper.writer(filters);
            }
            else {
                objectWriter = currentMapper.writer();
            }
            if (javaType != null && javaType.isContainerType()) {
                objectWriter = objectWriter.withType(javaType);
            }
            objectWriter.writeValue(generator, value);

            writeSuffix(generator, object);
            generator.flush();

        }
        catch (JsonProcessingException ex) {
            throw new HttpMessageNotWritableException("Could not write content: " + ex.getMessage(), ex);
        }
    }
}

弗兰克

另一答案

我喜欢Franck Lefebure's方法,但我不喜欢使用反射,所以这里是使用自定义PrettyFormattedBody类型+非常格式化的数组/列表的解决方案:

弹簧配置:

@Bean
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
    return new CustomJsonResponseMapper();
}

custom JSON response mapper.Java:

public class CustomJsonResponseMapper extends MappingJackson2HttpMessageConverter {

    private final ObjectMapper prettyPrintObjectMapper;

    public CustomJsonResponseMapper() {
        super();
        prettyPrintObjectMapper = initiatePrettyObjectMapper();
    }

    protected ObjectMapper initiatePrettyObjectMapper() {
        // clone and re-configure default object mapper
        final ObjectMapper prettyObjectMapper = objectMapper != null ? objectMapper.copy() : new ObjectMapper();
        prettyObjectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);

        // for arrays - use new line for every entry
        DefaultPrettyPrinter pp = new DefaultPrettyPrinter();
        pp.indentArraysWith(new DefaultIndenter());
        prettyObjectMapper.setDefaultPrettyPrinter(pp);

        return prettyObjectMapper;
    }

    @Override
    protected void writeInternal(final Object objectToWrite, final Type type, final HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

        // based on: if objectToWrite is PrettyFormattedBody with isPretty == true => use custom formatter
        // otherwise - use the default one

        final Optional<PrettyFormattedBody> prettyFormatted = Optional.ofNullable(objectToWrite)
                .filter(o -> o instanceof PrettyFormattedBody)
                .map(o -> (PrettyFormattedBody) objectToWrite);

        final boolean pretty = prettyFormatted.map(PrettyFormattedBody::isPretty).orElse(false);
        final Object realObject = prettyFormatted.map(PrettyFormattedBody::getBody).orElse(objectToWrite);

        if (pretty) {
            // this is basically full copy of super.writeInternal(), but with custom (pretty) object mapper
            MediaType contentType = outputMessage.getHeaders().getContentType();
            JsonEncoding encoding = getJsonEncoding(contentType);
      

以上是关于如何在Spring MVC中基于http请求头启用json的动态漂亮打印?的主要内容,如果未能解决你的问题,请参考以下文章

Ajax+Spring MVC实现跨域请求(JSONP)(转)

在spring mvc测试中访问请求体和请求头

如何使用 Spring MVC 和 Spring Security 为资源处理程序启用 HTTP 缓存

Http请求中Accept、Content-Type讲解以及在Spring MVC中的应用

springmvc 请求头和请求体 有啥用

如何使用 Angular 2 在 POST 上启用 ASP.NET MVC 4 中的跨源请求