17

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

这个想法是生成一些 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?

4

2 回答 2

24

当您意识到HandlerMethodReturnValueHandler新的 JSR 353 API 没有什么特别之处时,答案就很简单了。相反,在这种情况下,RequestResponseBodyMethodProcessor(for @ResponseBody) 使用 aMappingJackson2HttpMessageConverter来序列化处理程序方法的返回值。

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

假设您正在使用GlassfishJSR 353 的提供者实现,这些类是org.glassfish.json.JsonObjectBuilderImpl$JsonObjectImplorg.glassfish.json.JsonStringImplorg.glassfish.json.JsonNumberImpl, 和javax.json.JsonValue$3( value 的匿名类FALSE)。

因为JsonObjectImpl(your result, ie. root, object) 是一个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,稍微复杂一点,但更有价值。

于 2013-10-06T03:29:51.557 回答
1

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<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);
}
}
于 2016-12-09T19:02:46.883 回答