在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$JsonObjectImpl
, org.glassfish.json.JsonStringImpl
和org.glassfish.json.JsonNumberImpl
,以及javax.json.JsonValue$3
(值为FALSE
的匿名类)。
因为JsonObjectImpl
(您的结果,即根,对象)是Map
(特殊类型),所以ObjectMapper
将地图的条目序列化为JSON键值对元素,其中Map键是JSON键,Map值是JSON值。 对于密钥,它工作正常,序列化为name
, age
和married
。 对于该值,它使用我上面提到的类和它们各自的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); } }