Подтвердить что ты не робот

Возврат JsonObject с помощью @ResponseBody в SpringMVC

Я использую новый Java API (JSR 353) для JSON в проекте SpringMVC.

Идея состоит в том, чтобы сгенерировать часть данных 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?

4b9b3361

Ответ 1

Ответ довольно прост, когда вы понимаете, что для нового API JSR 353 нет специального HandlerMethodReturnValueHandler. Вместо этого в этом случае RequestResponseBodyMethodProcessor (для @ResponseBody) использует MappingJackson2HttpMessageConverter для сериализации возвращаемого значения вашего метода обработчика.

Внутри MappingJackson2HttpMessageConverter используется ObjectMapper. По умолчанию ObjectMapper использует геттеры класса для сериализации объекта в JSON.

Предполагая, что вы используете реализацию провайдера Glassfish JSR 353, эти классы org.glassfish.json.JsonObjectBuilderImpl$JsonObjectImpl, org.glassfish.json.JsonStringImpl и org.glassfish.json.JsonNumberImpl и javax.json.JsonValue$3 (анонимный класс для значения FALSE).

Поскольку JsonObjectImpl (ваш результат, т.е. root, object) является Map (особый тип), ObjectMapper сериализует записи в виде элементов пары ключей ключа JSON, где ключ карты является ключом JSON, а значение карты - значение JSON. Для ключа он отлично работает, сериализуется как name, age и married. Для значения используются классы, упомянутые выше, и их соответствующие геттеры. Например, 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 для сериализации объекта 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
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);
}
}