Devolver JsonObject usando @ResponseBody en SpringMVC

Estoy usando la nueva API de Java (JSR 353) para JSON en un proyecto de SpringMVC.

La idea es generar algunos datos de Json y devolverlos al cliente. El controlador que tengo se parece a esto:

@RequestMapping("/test") @ResponseBody public JsonObject test() { JsonObject result = Json.createObjectBuilder() .add("name", "Dade") .add("age", 23) .add("married", false) .build(); return result; } 

Y cuando accedo a esto, en lugar de obtener la representación esperada del JSON, obtengo estos en su lugar:

 {"name":{"chars":"Dade","string":"Dade","valueType":"STRING"},"age":{"valueType":"NUMBER","integral":true},"married":{"valueType":"FALSE"}} 

¿Por qué es esto? Que esta pasando? ¿Y cómo hago que devuelva el JSON esperado correctamente?

La respuesta es bastante simple cuando te das cuenta de que no hay HandlerMethodReturnValueHandler especial para la nueva API JSR 353. En cambio, en este caso, RequestResponseBodyMethodProcessor (para @ResponseBody ) utiliza un MappingJackson2HttpMessageConverter para serializar el valor de retorno de su método de controlador.

Internamente, el MappingJackson2HttpMessageConverter utiliza un ObjectMapper . De forma predeterminada, ObjectMapper usa los getters de una clase para serializar un objeto a JSON.

Asumiendo que está utilizando la implementación del proveedor de Glassfish del JSR 353, esas clases son org.glassfish.json.JsonObjectBuilderImpl$JsonObjectImpl , org.glassfish.json.JsonStringImpl , y org.glassfish.json.JsonNumberImpl , y javax.json.JsonValue$3 (una clase anónima para el valor FALSE ).

Debido a que JsonObjectImpl (su resultado, es decir, raíz, objeto) es un Map (tipo especial), ObjectMapper serializa las entradas del mapa como elementos pares JSON clave-valor, donde la clave Map es la clave JSON, y el valor Map es el valor JSON . Para la clave, funciona bien, serializando como name , age y married . Por el valor, utiliza las clases que mencioné anteriormente y sus respectivos getters. Por ejemplo, org.glassfish.json.JsonStringImpl se implementa como

 final class JsonStringImpl implements JsonString { private final String value; public JsonStringImpl(String value) { this.value = value; } @Override public String getString() { return value; } @Override public CharSequence getChars() { return value; } @Override public ValueType getValueType() { return ValueType.STRING; } ... } 

Por lo tanto, ObjectMapper utiliza los getters de Java Bean para serializar el objeto JsonStringImpl (que es el valor de Map Entry), como

 {"chars":"Dade","string":"Dade","valueType":"STRING"} 

Lo mismo aplica para los otros campos.

Si quiere escribir correctamente el JSON, simplemente devuelva un String .

 @RequestMapping("/test", produces="application/json") @ResponseBody public String test() { JsonObject result = Json.createObjectBuilder() .add("name", "Dade") .add("age", 23) .add("married", false) .build(); return result.toString(); } 

O HandlerMethodReturnValueHandler tu propio HandlerMethodReturnValueHandler , un poco más complicado, pero más gratificante.

La respuesta de Sotirios Delimanolis sí funciona, pero en mi caso tuve que asegurarme de que el orden correcto de HttpMessageConverter estuviera en su lugar. Esto se debe a que también necesitaba convertir los valores de JodaTime al formato ISO 8601. Esta configuración personalizada de WebMvcConfigurerAdapter funcionó para mí:

 @Configuration public class WebConfiguration extends WebMvcConfigurerAdapter { @SuppressWarnings("UnusedDeclaration") private static final Logger log = LoggerFactory.getLogger(WebConfiguration.class); public void configureMessageConverters(List> converters) { log.info("Configuring jackson ObjectMapper"); final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); final ObjectMapper objectMapper = new ObjectMapper(); //configure Joda serialization objectMapper.registerModule(new JodaModule()); objectMapper.configure(com.fasterxml.jackson.databind.SerializationFeature. WRITE_DATES_AS_TIMESTAMPS, false); // Other options such as how to deal with nulls or identing... objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); objectMapper.enable(SerializationFeature.INDENT_OUTPUT); converter.setObjectMapper(objectMapper); StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); /* StringHttpMessageConverter must appear first in the list so that Spring has a chance to use it for Spring RestController methods that return simple String. Otherwise, it will use MappingJackson2HttpMessageConverter and clutter the response with escaped quotes and such */ converters.add(stringHttpMessageConverter); converters.add(converter); super.configureMessageConverters(converters); } }