3

I am using Spring boot, Spring MVC and Jackson. I am writing a RESTful API and one of the requirements is to prettify the response (json) if only the request contains the parameter prettify.

For example, GET /examples/1 must answer with: {"id":1,"name":"example"}, and GET /examples/1?prettify with:

{
    "id": 1,
    "name": "example"
}

I know I can define my own ObjectMapper but I cannot make it depending on the request. I can set spring.jackson.serialization.INDENT_OUTPUT to true but I cannot display a response not prettified.

Does someone have an idea about this?

Ali Dehghani
  • 46,221
  • 15
  • 164
  • 151
Happy
  • 1,815
  • 2
  • 18
  • 33
  • Just a "loud thinking" but what I would try is to inject `Jackson2ObjectMapper` somewhere in your REST API controller, and then call its method `indentOutput (boolean)` depending on request. I believe that this topic may help: http://stackoverflow.com/questions/28324352/how-to-customise-the-jackson-json-mapper-implicitly-used-by-spring-boot – patrykos91 Feb 07 '16 at 21:22
  • Problem is, I can inject a new Jackson2ObjectMapper but, after the injection (done when the application starts), I do not see a way to "inject" the request in the Jackson2ObjectMapper – Happy Feb 08 '16 at 16:15

1 Answers1

2

Ok, I've done it.

So, the way to do it, is to use injected Jackson2ObjectMapper to manually serialize response :)

Lets look on simple main application class:

@SpringBootApplication
public class StackoverflowApplication {

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

    @Bean
    public Jackson2ObjectMapperBuilder objectMapperBuilder() {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.indentOutput(true);
        return builder;
     }
}

Some rest controller class:

@RestController
public class TestRestController {
    private static final Logger LOGGER = Logger.getLogger(TestRestController.class);

    @Autowired
    Jackson2ObjectMapperBuilder objectMapperBuilder;

    @RequestMapping(value = "/testendpoint/{somevalue}", method = RequestMethod.GET, produces="application/json")
    public @ResponseBody String customersLastVisit(@PathVariable(value = "somevalue") Integer number,
            @RequestParam(value = "pretify", defaultValue = "false") Boolean pretify) throws JsonProcessingException {
        if (pretify) {
            LOGGER.info("Pretify response!");
            objectMapperBuilder.indentOutput(true);
        }
        else {
            objectMapperBuilder.indentOutput(false);
        }

        ObjectMapper mapper = objectMapperBuilder.build();
        String jsonResponse = mapper.writeValueAsString(new TestDTO());
        return jsonResponse;
    }
}

And DTO class:

public class TestDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    private Integer id;
    private String name;

    public TestDTO() {
        super();
        id = new Integer(1);
        name = new String("SomeName");
    }
    //getters, setters etc ...
}

http://localhost:8080/testendpoint/1 returns {"id":1,"name":"SomeName"} while http://localhost:8080/testendpoint/1?pretify=true returns

{
  "id" : 1,
  "name" : "SomeName"
}

Edit: If you want to use it for every controller do like this:

public class PretifyingInterceptor extends HandlerInterceptorAdapter {

    private static final Logger LOGGER = Logger.getLogger(PretifyingInterceptor.class);

    @Autowired
    MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        String pretify = request.getParameter("pretify");
        Boolean isPretify = Boolean.parseBoolean(pretify);

        LOGGER.info("Should be pretified: " + isPretify);

        if (isPretify) {
            mappingJackson2HttpMessageConverter.setPrettyPrint(true);
        }
        else {
            mappingJackson2HttpMessageConverter.setPrettyPrint(false);
        }
        return true;
    }
}

Now add new interceptor:

@Configuration
public class InterceptorConfigurerAdapter extends WebMvcConfigurerAdapter{


    @Bean
    PretifyingInterceptor pretifyingInterceptor() {
        return new PretifyingInterceptor();
    }

     @Override
        public void addInterceptors(final InterceptorRegistry registry) {
            registry.addInterceptor(pretifyingInterceptor())
                    .addPathPatterns("/**");
        }

}

And now controller can look like:

@RequestMapping(value = "/testendpoint/{somevalue}", method = RequestMethod.GET, produces = "application/json")
    public @ResponseBody TestDTO customersLastVisit(@PathVariable(value = "somevalue") Integer number,
            @RequestParam(value = "pretify", defaultValue = "false") Boolean pretify) throws JsonProcessingException {
        return new TestDTO();
    }

Edit2: To avoid some 'between-request shared state of Interceptor', define the Interceptor bean with scope request.

Hope that helps :)

patrykos91
  • 3,506
  • 2
  • 24
  • 30
  • You can just autowire the ObjectMapper and use `writerWithDefaultPrettyPrinter()`. – Cyril Feb 08 '16 at 18:05
  • So here we have a way to do it for one request. But we would like to do it for every requests, with an interceptor I suppose. I'm looking for override the Jackson interceptor but I haven't find a way yet. – Happy Feb 08 '16 at 18:25
  • @patrykos91 Thanks for your update. Looking at your answer, I suppose we will have some concurrency problems. For example: Thread A is stopped at the end of the method `preHandle` with `pretiffy=true`. Thread B does the whole job with `pretiffy=false`. Thread A ends the job: the result won't be pretiffied. I think we need to reimplement the json serializer. – Happy Feb 10 '16 at 15:10
  • Not necessarily. Better test it out before You start json serlializer reimplementation. I think it all depends on what is the scope for `objectMapper` that is obtained by `MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;`. If its scoped as `request` every request will have its own instance, and nothing will collide. – patrykos91 Feb 10 '16 at 16:33
  • After a few benchmarks it seems you are right. There is no multithreading issue. Thanks a lot for your help. – Happy Apr 03 '16 at 21:36
  • EDIT : a few more benchmarks and it doesn't work good anymore. I am looking for a good way, I'll post my answer if I find it. – Happy Apr 04 '16 at 10:03
  • What precisely is wrong with my solution? Maybe I can help some more. – patrykos91 Apr 04 '16 at 11:46