在SpringMVC中使用@ResponseBody返回JsonObject

我在SpringMVC项目中使用新的Java API(JSR 353)来实现JSON。

我们的想法是生成一些Json数据并将其返回给客户端。 控制器我看起来有点像这样:

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

当我访问它时,而不是获得JSON的预期表示,我得到这些:

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

为什么是这样? 到底是怎么回事? 如何让它正确返回预期的JSON?

当您意识到新的JSR 353 API没有特殊的HandlerMethodReturnValueHandler时,答案非常简单。 相反,在这种情况下, RequestResponseBodyMethodProcessor (对于@ResponseBody )使用MappingJackson2HttpMessageConverter来序列化处理程序方法的返回值。

在内部, MappingJackson2HttpMessageConverter使用ObjectMapper 。 默认情况下, ObjectMapper使用类的getter将对象序列化为JSON。

假设您正在使用Glassfish的JSR 353的提供程序实现,那些类是org.glassfish.json.JsonObjectBuilderImpl$JsonObjectImplorg.glassfish.json.JsonStringImplorg.glassfish.json.JsonNumberImpl ,以及javax.json.JsonValue$3 (值为FALSE的匿名类)。

因为JsonObjectImpl (您的结果,即根,对象)是Map (特殊类型),所以ObjectMapper将地图的条目序列化为JSON键值对元素,其中Map键是JSON键,Map值是JSON值。 对于密钥,它工作正常,序列化为nameagemarried 。 对于该值,它使用我上面提到的类和它们各自的getter。 例如, org.glassfish.json.JsonStringImpl实现为

 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使用Java Bean getter来序列化JsonStringImpl对象(即Map Entry的值),如

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

这同样适用于其他领域。

如果要正确编写JSON,只需返回一个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(); } 

或者制作自己的HandlerMethodReturnValueHandler ,有点复杂,但更有价值。

Sotirios Delimanolis的答案确实有效,但在我的情况下,我必须确保正确的HttpMessageConverter订单到位。 这是因为我还需要将JodaTime值转换为ISO 8601格式。 此自定义WebMvcConfigurerAdapter配置适用于我:

 @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); } }