Имеет ли десериализация Джексона максимальную глубину наследования?

Другими словами, существует ли предел глубины наследования, которого можно достичь. В настоящее время я нахожусь на глубине 2, дедушка и бабушка -> родитель -> ребенок, и я сталкиваюсь с проблемой, когда Джексон может десериализоваться до родителя, а затем выдает UnrecognizedPropertyException. Это было бы правильно, однако дочерний класс обладает этим свойством, и я считаю, что добавил правильную информацию о типе для Джексона, чтобы десериализовать дочерний элемент.

Этот тест показывает проблему:

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Value;
import lombok.experimental.SuperBuilder;
import org.junit.Assert;
import org.junit.Test;

import java.io.IOException;
import java.util.List;

public class JacksonInheritanceTest {

    @Test
    public void deserializeChildrenAsGrandParentList() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        String grandparentsJson = "{" +
                "\"list\":[{" +
                "\"type\": \"parent\"," +
                "\"value\": \"child\"," +
                "\"someProperty\": \"foobar\"" +
                "}]" +
                "}";
        GrandParentList grandparents = mapper.readValue(grandparentsJson, GrandParentList.class);
        Assert.assertNotNull(grandparents);
    }

    @Test
    public void deserializeParentAsGrandParent() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        String parentJson = "{" +
                "\"type\": \"parent\"," +
                "\"value\": \"child\"" +
                "}";
        GrandParent grandparent = mapper.readValue(parentJson, GrandParent.class);
        Assert.assertNotNull(grandparent);
    }

    @Test
    public void deserializeChildAsGrandParent() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        String grandparentJson = "{" +
                "\"type\": \"parent\"," +
                "\"value\": \"child\"," +
                "\"someProperty\": \"foobar\"" +
                "}";
        GrandParent grandparent = mapper.readValue(grandparentJson, GrandParent.class);
        Assert.assertNotNull(grandparent);
    }

    @Test
    public void deserializeChildAsParent() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        String childJson = "{" +
                "\"type\": \"parent\"," +
                "\"value\": \"child\"," +
                "\"someProperty\": \"foobar\"" +
                "}";
        Parent parent = mapper.readValue(childJson, Parent.class);
        Assert.assertNotNull(parent);
    }

    @Test
    public void deserializeAsChild() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        String child1 = "{" +
                "\"type\": \"parent\"," +
                "\"value\": \"child\"," +
                "\"someProperty\": \"foobar\"" +
                "}";
        Child child = mapper.readValue(child1, Child.class);
        Assert.assertNotNull(child);
    }
}

class GrandParentList {
    @JsonProperty
    List<GrandParent> list;
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = Parent.class,
                name = "parent")
})
@Getter
@SuperBuilder
@JsonDeserialize(builder = GrandParent.GrandParentBuilderImpl.class)
class GrandParent {
    @JsonProperty("type")
    private String type;

    @JsonPOJOBuilder(withPrefix = "")
    static final class GrandParentBuilderImpl extends GrandParentBuilder<GrandParent, GrandParent.GrandParentBuilderImpl> {
    }
}


@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "value", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = Child.class, name = "child")
})
@Getter
@SuperBuilder
@JsonDeserialize(builder = Parent.ParentBuilderImpl.class)
class Parent extends GrandParent {
    @JsonProperty
    private String value;

    @JsonPOJOBuilder(withPrefix = "")
    static final class ParentBuilderImpl extends ParentBuilder<Parent, ParentBuilderImpl> {
    }
}


@EqualsAndHashCode(callSuper = true)
@Value
@SuperBuilder
@JsonDeserialize(builder = Child.ChildBuilderImpl.class)
class Child extends Parent {
    @JsonProperty
    private String someProperty;

    @JsonPOJOBuilder(withPrefix = "")
    static final class ChildBuilderImpl extends ChildBuilder<Child, ChildBuilderImpl> {
    }
}

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

user207421 24.06.2019 13:15

Этот пример полный, но не минимальный. что бы я ни знал, пока нет предела, мне кажется, что вы не определили «дочерний элемент» как подкласс прародителя или родителя, а дочерний элемент не аннотирован с помощью jsontype. еще пример jsonTypeName: stackoverflow.com/questions/56395909/…

pdem 24.06.2019 13:32
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
2
875
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Установите конфигурацию для объекта ObjectMapper:

mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

К сожалению, это не ответ на мой вопрос. Так как это приведет к тому, что Джексон просто проигнорирует свойство ChildsomeProperty и вернет экземпляр Parent вместо Child. Это не то поведение, которое я ожидаю.

Byebye 24.06.2019 12:27
Ответ принят как подходящий

Вот как определить наследование со многими уровнями:

Вы хотите десериализовать список GrandParent, конечный тип которого является «дочерним».

  {
      "list":[{
          "type": "child",
          "someProperty": "foobar"
      }]
  }

и дерево наследования:

GrandParent
  Parent
    Child(someProperty:String)

Вы должны определить свой атрибут «тип» на верхнем уровне, @JsonTypeInfo(...) вы можете повторить это на подуровнях, но это не требуется, если вы сериализуете/десериализуете только дедушку и бабушку. Затем на каждом родительском уровне (классы Parent и GrandParent) вы определяете подтипы так же, как вы это делали с @JsonSubTypes.

код

public class JacksonInheritanceTest2 {

    @Test
    public void deserializeChildrenAsGrandParentList() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        String grandparentsJson = "{" +
                "\"list\":[{" +
                "\"type\": \"child\"," +
                "\"someProperty\": \"foobar\"" +
                "}]" +
                "}";
        GrandParentList grandparents = mapper.readValue(grandparentsJson, GrandParentList.class);
        Assertions.assertNotNull(grandparents);
    }


}

class GrandParentList {
    @JsonProperty
    List<GrandParent> list;
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = Parent.class,name = "parent"),
        //@JsonSubTypes.Type(value = Child.class, name = "child")
})
class GrandParent {
    @JsonProperty("type")
    private String type;

}


//@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = Child.class, name = "child")
})
class Parent extends GrandParent {
    @JsonProperty
    private String value;

}

@JsonSubTypes({
    @JsonSubTypes.Type(value = Child.class, name = "child")
})
class Child extends Parent {
    @JsonProperty
    private String someProperty;

    public String getSomeProperty() {
        return someProperty;
    }

    public void setSomeProperty(String someProperty) {
        this.someProperty = someProperty;
    }



}

Ошибка, которую вы сделали:

  • определить несколько имен типов атрибутов, каждое для одного родительского уровня: вы выбираете имя типа атрибута и используете только одно.
  • в Json вы установили имя типа родителя в аннотации: важен только тип листа, остальная часть дерева вычитается.

Боковой узел: Assertions от junit5, он делает то же, что и Assert от junit4

Так что если я правильно понимаю. Джексон из коробки может обрабатывать вывод типа только на основе одного имени свойства? Моя проблема в том, что мне нужно сделать вывод типа на основе двух свойств, а не одного. Я предположил, что добавление @JsonTypeInfo к разным глубинам наследования позволит Джексону сделать вывод, какое свойство будет отображаться на какую глубину, и соединить их вместе. К сожалению, похоже, мне нужно написать что-то специальное для этого. Спасибо за ответ!

Byebye 24.06.2019 14:40

@ByeBye нет, вывод типа работает должным образом, ваш объект имеет только один тип за раз. Вы не можете указать 2 типа для объекта (как бы вы поступили с java-кодом?)

pdem 24.06.2019 16:50

@ByeBy, может быть, я мог бы объяснить, как работает самоанализ: 1. вы даете класс дедушки и бабушки с его конфигурацией 2. Джексон находит аннотацию к классу -> получает имя типа объекта - затем рекурсивно сканирует, чтобы найти реальный класс, связанный с это имя 3. Джексон создает экземпляр этого класса (не нужно знать промежуточный класс Parent на этом уровне) 3. это влияет на все найденные поля, дающие дочерний класс - снова все поля Parent известны по наследству - не нужно укажите Родитель. Во всех тезисах 3 шага вам никогда не нужно указывать, что экземпляр «Дочерний» является «Родительским».

pdem 24.06.2019 17:44

Да, это то, что я имел в виду. Я могу указать информацию о типе в аннотации только один раз (на GrandParent), и я могу использовать только одно свойство для вывода типа (свойство type). Я отмечу ваш ответ как ответ. Спасибо.

Byebye 24.06.2019 20:07

Другие вопросы по теме