Returning JsonObject using @ResponseBody in SpringMVC

匿名 (未验证) 提交于 2019-12-03 02:08:02

问题:

I am using the new Java API (JSR 353) for JSON in a SpringMVC project.

The idea is to generate some piece of Json data and have it returned to the client. The controller I have look somewhat like this:

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

And when I access this, instead of getting the expected representation of the JSON, I get these instead:

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

Why is this? What is going on? And how do I make it returned the expected JSON properly?

回答1:

The answer is pretty simple when you realize there is no special HandlerMethodReturnValueHandler for the new JSR 353 API. Instead, in this case, the RequestResponseBodyMethodProcessor (for @ResponseBody) uses a MappingJackson2HttpMessageConverter to serialize the return value of your handler method.

Internally, the MappingJackson2HttpMessageConverter uses an ObjectMapper. By default, the ObjectMapper uses the getters of a class to serialize an object to JSON.

Assuming you are using Glassfish's provider implementation of the JSR 353, those classes are org.glassfish.json.JsonObjectBuilderImpl$JsonObjectImpl, org.glassfish.json.JsonStringImpl, and org.glassfish.json.JsonNumberImpl, and javax.json.JsonValue$3 (an anonymous class for the value FALSE).

Because JsonObjectImpl (your result, ie. root, object) is a Map (special type), ObjectMapper serializes the map's entries as JSON key-value pair elements, where the Map key is the JSON key, and the Map value is the JSON value. For the key, it works fine, serializing as name, age, and married. For the value, it uses the classes I mentioned above and their respective getters. For example, org.glassfish.json.JsonStringImpl is implemented as

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

ObjectMapper therefore uses the Java Bean getters to serialize the JsonStringImpl object (that is the Map Entry's value), as

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

The same applies for the other fields.

If you want to correctly write the JSON, simply return a 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(); } 

Or make your own HandlerMethodReturnValueHandler, a little more complicated, but more rewarding.



回答2:

The answer from Sotirios Delimanolis does indeed work, but in my case I had to ensure the proper HttpMessageConverter order was in place. This is because I needed to also convert JodaTime values to ISO 8601 format. This custom WebMvcConfigurerAdapter Configuration worked for me:

@Configuration public class WebConfiguration extends WebMvcConfigurerAdapter {  @SuppressWarnings("UnusedDeclaration") private static final Logger log = LoggerFactory.getLogger(WebConfiguration.class);  public void configureMessageConverters(List<HttpMessageConverter<?>> 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); } } 


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!