在 SpringMVC 中使用@ResponseBody返回 JsonObject

2022-09-03 01:15:06

我正在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?


答案 1

当您意识到新的JSR 353 API没有特殊之处时,答案非常简单。相反,在本例中,(for ) 使用 a 序列化处理程序方法的返回值。HandlerMethodReturnValueHandlerRequestResponseBodyMethodProcessor@ResponseBodyMappingJackson2HttpMessageConverter

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

假设您使用的是 JSR 353 的提供程序实现,则这些类是 、 和 和 和 (值的匿名类)。Glassfishorg.glassfish.json.JsonObjectBuilderImpl$JsonObjectImplorg.glassfish.json.JsonStringImplorg.glassfish.json.JsonNumberImpljavax.json.JsonValue$3FALSE

因为(您的结果,即根,对象)是一个(特殊类型),将映射的条目序列化为 JSON 键值对元素,其中映射键是 JSON 键,映射值是 JSON 值。对于该键,它工作正常,序列化为 、 和 。对于值,它使用我上面提到的类及其各自的 getter。例如,实现为JsonObjectImplMapObjectMappernameagemarriedorg.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 getters 序列化对象(即 Map Entry 的值),如下所示JsonStringImpl

{"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


答案 2

Sotirios Delimanolis的答案确实有效,但在我的情况下,我必须确保正确的HttpMessageConverter顺序到位。这是因为我还需要将JodaTime值转换为ISO 8601格式。这个自定义的WebMvcConfigurerAdapter Configuration为我工作:

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