Де-сериализация JSON для полиморфной объектной модели с использованием аннотации Spring и JsonTypeInfo

У меня есть следующая объектная модель в моем веб-приложении Spring MVC (v3.2.0.RELEASE):

public class Order {
  private Payment payment;
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.WRAPPER_OBJECT)
@JsonSubTypes.Type(name = "creditCardPayment", value = CreditCardPayment.class)
public interface Payment {}


@JsonTypeName("creditCardPayment")
public class CreditCardPayment implements Payment {}

Когда я сериализую класс Order в JSON, я получаю следующий результат (именно это я хочу):

{
  "payment" : {
    "creditCardPayment": {
      ...
    } 
}

К сожалению, если я возьму вышеуказанный JSON и попытаюсь де-сериализовать его обратно в мою объектную модель, я получаю следующее исключение:

Не удалось прочитать JSON: не удалось разрешить идентификатор типа 'creditCardPayment' в подтип [простой тип, класс Оплата] в [Источник: [email protected]; линия 1, столбец: 58] (через ссылочную цепочку: Order [ "payment" ]); вложенными exception is com.fasterxml.jackson.databind.JsonMappingException: Не удалось разрешить идентификатор типа 'creditCardPayment' в подтип [простой тип, класс Оплата] в [Источник: [email protected]; линия 1, столбец: 58] (через цепочку ссылок: Order [ "payment" ])

Мое приложение настроено через Spring JavaConf, как показано ниже:

@Configuration
@EnableWebMvc
public class AppWebConf extends WebMvcConfigurerAdapter {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(Include.NON_NULL);
        objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
        return objectMapper;
    }

    @Bean
    public MappingJackson2HttpMessageConverter mappingJacksonMessageConverter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setObjectMapper(objectMapper());
        return converter;
    }

    @Bean
    public Jaxb2RootElementHttpMessageConverter jaxbMessageConverter() {
        return new Jaxb2RootElementHttpMessageConverter();
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(jaxbMessageConverter());
        converters.add(mappingJacksonMessageConverter());
    }
}

Для тестирования у меня есть контроллер с двумя методами, один возвращает запрос на запрос HTTP для GET (этот работает) и тот, который принимает заказ через HTTP POST (этот не удается), например

@Controller
public class TestController {

    @ResponseBody
    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public Order getTest() {}

    @RequestMapping(value = "/test", method = RequestMethod.POST)
    public void postTest(@RequestBody order) {}

}

Я пробовал все предложения из различных обсуждений по SO, но пока не повезло. Может ли кто-нибудь определить, что я делаю неправильно?

Ответы

Ответ 1

Попробуйте зарегистрировать подтип, используя ObjectMapper.registerSubtypes вместо использования аннотаций

Ответ 2

Используется метод registerSubtypes()!

@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
public interface Geometry {
  //...
}

public class Point implements Geometry{
  //...
}
public class Polygon implements Geometry{
  //...
}
public class LineString implements Geometry{
  //...
}

GeoJson geojson= null;
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerSubtypes(Polygon.class,LineString.class,Point.class);

try {
    geojson=mapper.readValue(source, GeoJson.class);            
} catch (IOException e) {
    e.printStackTrace();
}

Примечание1: Мы используем интерфейс и реализующие классы. Я хочу, чтобы Джексон де-сериализовал классы в соответствии с их классами реализации, вы должны зарегистрировать их все, используя метод ObjectMapper "registerSubtypes".

Примечание2: Кроме того, вы используете "@JsonTypeInfo (use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property =" type ")" в качестве аннотации к вашему интерфейсу.

Вы также можете определить порядок свойств, когда mapper записывает значение вашего POJO в качестве json.

Это можно сделать, используя аннотацию ниже.

@JsonPropertyOrder({"type","crs","version","features"})
public class GeoJson {

    private String type="FeatureCollection";
    private List<Feature> features;
    private String version="1.0.0";
    private CRS crs = new CRS();
    ........
}

Надеюсь, это поможет!

Ответ 3

У меня была аналогичная проблема при работе с сервисом на основе dropwizard. Я не совсем понимаю, почему что-то не работает для меня так же, как работает код dropwizard, но я знаю, почему код в исходном сообщении не работает. @JsonSubTypes требуется массив подтипов, а не одно значение. Поэтому, если вы замените строку...

@JsonSubTypes.Type(name = "creditCardPayment", value = CreditCardPayment.class)

с...

@JsonSubTypes({ @JsonSubTypes.Type(name = "creditCardPayment", value = CreditCardPayment.class) })

Я верю, что ваш код будет работать.

Для тех, кто имеет такое же сообщение об ошибке, может возникнуть проблема с обнаруженными подтипами. Попробуйте добавить строку, подобную приведенной выше, или искать проблему с открытием классов, в которых есть тег @JsonTypeName.

Ответ 4

Ответ Rashmin работал, и я нашел альтернативный способ избежать проблемы com.fasterxml.jackson.databind.JsonMappingException: Could not resolve type id into a subtype of Blah без использования registerSubtypes. Что вы можете сделать, так это добавить в родительский класс следующую аннотацию:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type")

Обратите внимание, что разница JsonTypeInfo.Id.CLASS вместо JsonTypeInfo.Id.NAME. Недостатком является то, что созданный JSON будет содержать все имя класса, включая полное пространство имен. Положительным моментом является то, что вам не нужно беспокоиться о регистрации подтипов.

Ответ 5

Обнаружена та же ошибка и используется эквивалент (вместо того, чтобы CreditCardPayment использовал мое имя класса) ниже JSON в качестве входа для Deserializer и он работал.

{    "тип": "CreditCardPayment",   ..... }

Ответ 6

В моем случае я добавил defaultImpl = SomeClass.class в @JsonTypeInfo и пытался его преобразовать SomeClass2.class