(Джексон) Как десериализовать JSON в POJO с полиморфизмом, используя свойство другого объекта? - PullRequest
0 голосов
/ 02 мая 2018

Допустим, у меня есть JSON с 2 объектами, "заголовок" и "данные". Обе они имеют эквивалентные модели, только у Data есть переменные атрибуты, а свойства Header всегда одинаковы. Свойства в «данных» являются переменными и должны быть десерилизованы в соответствии с полем из «заголовка», например:

// this should be deserialized to SuperPojo
{
  "header": {
    "type": "y" // this should be used as a rule for deserializing object "data"
  },
  "data": { // this should be deserialized into any Data subclass
    // variable data
  }
}

Вот бины, которые я хочу десериализовать вышеупомянутым JSON:

// wrapper object
@JsonIgnoreProperties(ignoreUnknown = true)
public class SuperPojo<T extends Data> {
    private Header header;
    private T data;
    // getters & setters
}

@JsonIgnoreProperties(ignoreUnknown = true)
public class Header implements Serializable {
    private String type;
    // getters & setters
}

// Marker class without fields. Used for inheritance and polymorphism purposes only
// the issue here is that Jackson tries to find a field named "type"
// within "Data" subclasses. What I want is for it to search the "type"
// attribute in class "Header".
@JsonTypeInfo(
    use = JsonTypeInfo.Id.CLASS,
    include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
    property = "type")
@JsonSubTypes({
    @JsonSubTypes.Type(value = DataX.class, name="x"), // how do I make this refer to field "type" in object "Header"?
    @JsonSubTypes.Type(value = DataY.class, name="y") 
})
public class Data implements Serializable { }

// should be deserialized into my SuperPojo when header.type equals "x"
@JsonTypeName("x")
public class DataX extends Data {
    private long id;
    private String name;
    // getters & setters
}

// should be deserialized into my SuperPojo when header.type equals "y"
@JsonTypeName("y")
public class DataY extends Data {
    @JsonProperty("registration_date")
    private LocalDateTime registrationDate;
    private Country country; // enum
    // getters & setters
}

Наконец, вот как я пытаюсь выполнить десериализацию:

// JSON X
{
  "header": {
    "type": "x"
  },
  "data": {
    "id": "12345",
    "name":"Jackson"
  }
}

// JSON Y
{
  "header": {
    "type": "y"
  },
  "data": {
    "registration_date": "2018-05-01T18:10:00",
    "country":"BRAZIL"
  }
}

@Service
public class MyTest {

    @Autowired
    private ObjectMapper mapper;

    void doStuff() {
        String jsonX = getJsonX(); // JSON X
        String jsonY = getJsonY(); // JSON Y
        SuperPojo<? extends Data> bigPojoX = mapper.readValue(jsonX, new TypeReference<SuperPojo<? extends Data>>() { });
        SuperPojo<? extends Data> bigPojoY = mapper.readValue(jsonY, new TypeReference<SuperPojo<? extends Data>>() { });
        DataX subPojoX = (DataX) bigPojoX.getData(); // throws ClassCastException
        DataY subPojoY = (DataY) bigPojoY.getData(); // throws ClassCastException
    }
}

То, что я делаю сейчас, так как я не мог найти способ сделать эту работу: я десериализую свой json в класс-оболочку с одним полем типа «Заголовок», затем получаю и проверяю "type" из него и десериализацию json снова в подкласс вышеупомянутого класса-оболочки с атрибутом правильной реализации Data.

Я уверен, что это не лучший способ сделать это, и не хороший выбор дизайна, но это был единственный способ, которым я мог решить эту проблему.

Я уже искал некоторые возможные решения, но проблемы немного отличались от моих, поэтому они также не работали:

1 Ответ

0 голосов
/ 03 мая 2018

Если у вас только простой скалярный идентификатор как header, как брат данных, вы можете легко сделать это, используя include = JsonTypeInfo.As.EXTERNAL_PROPERTY, как указано. Но это не работает для сложных объектов. Ваша лучшая ставка - полностью настраиваемый десериализатор для типа, который содержит данные и индикатор типа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...