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

Дезерминирование полиморфных типов с Джексоном

Если у меня есть такая структура класса:

public abstract class Parent {
    private Long id;
    ...
}

public class SubClassA extends Parent {
    private String stringA;
    private Integer intA;
    ...
}

public class SubClassB extends Parent {
    private String stringB;
    private Integer intB;
    ...
}

Есть ли альтернативный способ десериализации разных, чем @JsonTypeInfo? Используя эту аннотацию в моем родительском классе:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "objectType")

Я бы предпочел не принуждать клиентов моего API включать "objectType": "SubClassA" для десериализации подкласса Parent.

Вместо использования @JsonTypeInfo делает ли Джексон способ аннотировать подкласс и отличать его от других подклассов с помощью уникального свойства? В приведенном выше примере это будет примерно так: "Если объект JSON имеет "stringA": ... десериализует его как SubClassA, если он имеет "stringB": ... десериализует его как SubClassB".

4b9b3361

Ответ 1

Это похоже на то, что нужно использовать @JsonTypeInfo и @JsonSubTypes, но я просмотрел документы, и ни одно из свойств, которые могут быть предоставлены, похоже, похоже на то, что вы описываете.

Вы можете написать собственный десериализатор, который использует свойства @JsonSubTypes ' "name" и "value" нестандартным способом для выполнения того, что вы хотите. Десериализатор и @JsonSubTypes будут поставляться в базовом классе, а десериализатор будет использовать значения "name" для проверки наличия свойства и, если он существует, затем десериализовать JSON в класс, указанный в свойстве "значение", Тогда ваши классы будут выглядеть примерно так:

@JsonDeserialize(using = PropertyPresentDeserializer.class)
@JsonSubTypes({
        @Type(name = "stringA", value = SubClassA.class),
        @Type(name = "stringB", value = SubClassB.class)
})
public abstract class Parent {
    private Long id;
    ...
}

public class SubClassA extends Parent {
    private String stringA;
    private Integer intA;
    ...
}

public class SubClassB extends Parent {
    private String stringB;
    private Integer intB;
    ...
}

Ответ 2

Нет. Такая функция была запрошена - ее можно было бы назвать "вывод типа" или "подразумеваемый тип", но никто не придумал обоснованного общего предложения о том, как это должно работать. Легко думать о способах поддержки конкретных решений для конкретных случаев, но выяснение общего решения сложнее.

Ответ 3

Мое приложение требует от меня сохранения старой структуры, поэтому я нашел способ поддерживать полиморфизм без изменения данных. Вот что я делаю:

  • Расширить JsonDeserializer
  • Преобразуйте в Дерево и прочитайте поле, затем верните объект подкласса

    @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        JsonNode jsonNode = p.readValueAsTree(); 
        Iterator<Map.Entry<String, JsonNode>> ite = jsonNode.fields();
        boolean isSubclass = false;
        while (ite.hasNext()) {
            Map.Entry<String, JsonNode> entry = ite.next();
            // **Check if it contains field name unique to subclass**
            if (entry.getKey().equalsIgnoreCase("Field-Unique-to-Subclass")) {
                isSubclass = true;
                break;
            }
        }
        if (isSubclass) {
            return mapper.treeToValue(jsonNode, SubClass.class);
        } else {
            // process other classes
        }
    }
    

Ответ 4

Здесь решение, которое я придумал, немного расширилось для Эрика Гиллеспи. Он делает именно то, что вы просили, и это сработало для меня.

Использование Jackson 2.9

@JsonDeserialize(using = CustomDeserializer.class)
public abstract class BaseClass {

    private String commonProp;
}

// Important to override the base class' usage of CustomDeserializer which produces an infinite loop
@JsonDeserialize(using = JsonDeserializer.None.class)
public class ClassA extends BaseClass {

    private String classAProp;
}

@JsonDeserialize(using = JsonDeserializer.None.class)
public class ClassB extends BaseClass {

    private String classBProp;
}

public class CustomDeserializer extends StdDeserializer<BaseClass> {

    protected CustomDeserializer() {
        super(BaseClass.class);
    }

    @Override
    public BaseClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        TreeNode node = p.readValueAsTree();

        // Select the concrete class based on the existence of a property
        if (node.get("classAProp") != null) {
            return p.getCodec().treeToValue(node, ClassA.class);
        }
        return p.getCodec().treeToValue(node, ClassB.class);
    }
}

// Example usage
String json = ...
ObjectMapper mapper = ...
BaseClass instance = mapper.readValue(json, BaseClass.class);

Если вы хотите стать более привлекательным, вы можете расширить CustomDeserializer чтобы включить Map<String, Class<?>> который отображает имя свойства, которое при наличии сопоставляется с определенным классом. Такой подход представлен в этой статье.

Кстати, существует проблема github, запрашивающая это здесь: https://github.com/FasterXML/jackson-databind/issues/1627