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)
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.