Search code examples
spring-bootjacksonjackson2

Inject field into JSON response object


I'd like to inject a field into the response object during serialization. Is it possible to inject "success": "true" into the JSON response object? This needs to be a general solution for all parent response objects that are serialized.

For example with object:

public class UserResponse {
    private int id;
    private String firstName;
    private String lastName;
    private Organisation organisation;

    // getters setters
}

Jackson should return:

{
    "success": "true",
    "id": 1, 
    "firstName": "tom",
    "lastName": "jeffrey",
    "organisation": {
        // etc.
    }
}

I tried already with

public class CustomJsonSerializer extends StdSerializer<Object> {

    public CustomJsonSerializer() {
        this(null);
    }

    public CustomJsonSerializer(Class<Object> t) {
        super(t);
    }

    @Override
    public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("success", "true");
        jsonGenerator.writeObject(o);
        jsonGenerator.writeEndObject();
    }
}

But didn't succeed:

Could not write JSON: Can not start an object, expecting field name (context: Object); nested exception is com.fasterxml.jackson.core.JsonGenerationException: Can not start an object, expecting field name (context: Object)

Solution

  • You can use a ResponseBodyAdvice to modify the object returned by a controller before being serialized into JSON. Here is a trivial example which is applied by default to all responses:

    The view your controller returns

    public class Greeting {
        private static final String message = "Hello World!";
    
        public String getMessage() {
            return message;
        }
    }
    

    The response wrapper for setting a status

    public class ResponseWrapper {
        private Object data;
        private boolean success;
    
        public Object getData() {
            return data;
        }
    
        public void setData(Object data) {
            this.data = data;
        }
    
        public boolean isSuccess() {
            return success;
        }
    
        public void setSuccess(boolean success) {
            this.success = success;
        }
    }
    

    The controller

    @RestController
    @RequestMapping("/api/hello")
    public class HelloController {
        @GetMapping
        public Greeting hello() {
            return new Greeting();
        }
    }
    

    Test that validates the behavior

    @RunWith(SpringJUnit4ClassRunner.class)
    @WebMvcTest
    public class HelloControllerTest {
        @Autowired
        private MockMvc mockMvc;
        private static final String GREETING_ENDPOINT = "/api/hello";
    
    
        @Test
        public void returnsGreetingWithStatus() throws Exception {
            mockMvc.perform(get(GREETING_ENDPOINT))
                    .andExpect(jsonPath("$.data.message").value("Hello World!"))
                    .andExpect(jsonPath("$.success").value(true));
        }
    }
    

    The ResponseBodyAdvice

    @ControllerAdvice
    public class ResponseStatusAdvice implements ResponseBodyAdvice {
        @Override
        public boolean supports(MethodParameter returnType, Class converterType) {
            return converterType.equals(MappingJackson2HttpMessageConverter.class);
        }
    
        @Override
        public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
            ResponseWrapper wrapper = new ResponseWrapper();
            wrapper.setData(body);
            wrapper.setSuccess(true);
            return wrapper;
        }
    }
    

    This should give you a head start. On a side note, you can always take advantage of HTTP status codes. It is part of the protocol and will always be set.