Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to enable dynamic pretty print of json based on http request header in Spring MVC?

I want to pretty print json responses from Spring MVC Restcontrollers dynamically based on a http parameter (like suggested here: http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api#pretty-print-gzip).

I have found configurations for pretty print it by static configuration, but not how to do that dynamically?

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

Any idea how to do that?

like image 961
Jens Goldhammer Avatar asked Apr 16 '16 20:04

Jens Goldhammer


2 Answers

Introducing A New Media Type


You can define a new Media Type, say, application/pretty+json and register a new HttpMessageConverter that converts to that Media Type. In fact, if client sends a request with Accept: application/pretty+json header, our new HttpMessageConverter will write the response, Otherwise, the plain old MappingJackson2HttpMessageConverter would do that.

So, extends the MappingJackson2HttpMessageConverter like following:

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;
    }
}

That setPrettyPrint(true) in constructor will do the trick for us. Then we should register this HttpMessageConverter:

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

As i said, if client send a request with application/pretty+json Accept header, our PrettyPrintJsonConverter will write the JSON representation Prettily. Otherwise, MappingJackson2HttpMessageConverter would write a compact JSON to the response body.

You can achieve the same with a ResponseBodyAdvice or even Interceptors but in my opinion, registering a brand new HttpMessageConverter is the better approach.

like image 136
Ali Dehghani Avatar answered Oct 22 '22 20:10

Ali Dehghani


To switch to pretty rendering with a ?pretty=true parameter I use a custom 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;
}
}

The converter :

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

like image 32
Franck Lefebure Avatar answered Oct 22 '22 21:10

Franck Lefebure