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

Deserialize JSON с Джексоном в полиморфные типы - полный пример дает мне ошибку компиляции

Я пытаюсь работать через учебник от программиста Брюса, который должен позволить десериализацию полиморфного JSON.

Полный список можно найти здесь Учебники программиста Брюса (Великий материал кстати)

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

Во время компиляции я получаю следующую ошибку

Метод readValue (JsonParser, Class) в типе ObjectMapper неприменим для аргументов (ObjectNode, Class)

и это вызвано блоком кода

  public Animal deserialize(  
      JsonParser jp, DeserializationContext ctxt)   
      throws IOException, JsonProcessingException  
  {  
    ObjectMapper mapper = (ObjectMapper) jp.getCodec();  
    ObjectNode root = (ObjectNode) mapper.readTree(jp);  
    Class<? extends Animal> animalClass = null;  
    Iterator<Entry<String, JsonNode>> elementsIterator =   
        root.getFields();  
    while (elementsIterator.hasNext())  
    {  
      Entry<String, JsonNode> element=elementsIterator.next();  
      String name = element.getKey();  
      if (registry.containsKey(name))  
      {  
        animalClass = registry.get(name);  
        break;  
      }  
    }  
    if (animalClass == null) return null;  
    return mapper.readValue(root, animalClass);
  }  
} 

В частности, по строке

return mapper.readValue(root, animalClass);

Кто-нибудь сталкивался с этим раньше, и если да, было ли решение?

Буду признателен за любую помощь, которую любой может дать заранее спасибо Джон Д..

4b9b3361

Ответ 1

Как и было обещано, я помещаю пример использования аннотаций для сериализации/десериализации полиморфных объектов. Я основал этот пример в классе Animal из учебника, который вы читали.

Прежде всего, ваш класс Animal с Json Annotations для подклассов.

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Dog.class, name = "Dog"),

    @JsonSubTypes.Type(value = Cat.class, name = "Cat") }
)
public abstract class Animal {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Затем ваши подклассы Dog и Cat.

public class Dog extends Animal {

    private String breed;

    public Dog() {

    }

    public Dog(String name, String breed) {
        setName(name);
        setBreed(breed);
    }

    public String getBreed() {
        return breed;
    }

    public void setBreed(String breed) {
        this.breed = breed;
    }
}

public class Cat extends Animal {

    public String getFavoriteToy() {
        return favoriteToy;
    }

    public Cat() {}

    public Cat(String name, String favoriteToy) {
        setName(name);
        setFavoriteToy(favoriteToy);
    }

    public void setFavoriteToy(String favoriteToy) {
        this.favoriteToy = favoriteToy;
    }

    private String favoriteToy;

}

Как вы можете видеть, нет ничего особенного для Cat и Dog, единственный, кто знает о них, это abstract class Animal, поэтому при десериализации вы будете нацелены на Animal и ObjectMapper вернет фактический экземпляр, как вы можете видеть в следующем тесте:

public class Test {

    public static void main(String[] args) {

        ObjectMapper objectMapper = new ObjectMapper();

        Animal myDog = new Dog("ruffus","english shepherd");

        Animal myCat = new Cat("goya", "mice");

        try {
            String dogJson = objectMapper.writeValueAsString(myDog);

            System.out.println(dogJson);

            Animal deserializedDog = objectMapper.readValue(dogJson, Animal.class);

            System.out.println("Deserialized dogJson Class: " + deserializedDog.getClass().getSimpleName());

            String catJson = objectMapper.writeValueAsString(myCat);

            Animal deseriliazedCat = objectMapper.readValue(catJson, Animal.class);

            System.out.println("Deserialized catJson Class: " + deseriliazedCat.getClass().getSimpleName());



        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

Вывод после запуска класса Test:

{"@type":"Dog","name":"ruffus","breed":"english shepherd"}

Deserialized dogJson Class: Dog

{"@type":"Cat","name":"goya","favoriteToy":"mice"}

Deserialized catJson Class: Cat

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

Хосе Луис

Ответ 2

Простой способ включить полиморфную сериализацию/десериализацию через библиотеку Джексона состоит в том, чтобы глобально настроить средство отображения объектов Джексона (jackson.databind.ObjectMapper) для добавления информации, такой как конкретный тип класса, для определенных типов классов, таких как абстрактные классы.

Для этого просто убедитесь, что ваш маппер настроен правильно. Например:

Вариант 1. Поддержка полиморфной сериализации/десериализации для абстрактных классов (и классов с типами объектов)

jacksonObjectMapper.enableDefaultTyping(
    ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE); 

Вариант 2. Поддержка полиморфной сериализации/десериализации для абстрактных классов (и классов с типами объектов) и массивов этих типов.

jacksonObjectMapper.enableDefaultTyping(
    ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS); 

Ссылка: https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization

Ответ 3

Вам нужна только одна строка перед объявлением класса Animal для правильной полиморфной сериализации/десериализации:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public abstract class Animal {
   ...
}

Эта строка означает: добавить мета-свойство при сериализации или прочитать мета-свойство при десериализации (include = JsonTypeInfo.As.PROPERTY) с именем "@class" (property = "@class"), которое содержит полное имя класса Java (use = JsonTypeInfo.Id.CLASS).

Поэтому, если вы создаете JSON напрямую (без сериализации), не забудьте добавить мета-свойство "@class" с нужным именем класса для правильной десериализации.

Более подробная информация здесь

Ответ 4

Если вы используете quickxml, то

эти изменения могут потребоваться

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;

в основном методе -

использование

SimpleModule module =
  new SimpleModule("PolymorphicAnimalDeserializerModule");

вместо

new SimpleModule("PolymorphicAnimalDeserializerModule",
      new Version(1, 0, 0, null));

и в функции deserialize() для животных, сделайте следующие изменения

//Iterator<Entry<String, JsonNode>> elementsIterator =  root.getFields();
Iterator<Entry<String, JsonNode>> elementsIterator = root.fields();

//return mapper.readValue(root, animalClass);
return  mapper.convertValue(root, animalClass); 

Это работает для quickxml.jackson. Если он по-прежнему жалуется на поля класса. Используйте тот же формат, что и в json для имен полей (с "_" -underscore). как это //mapper.setPropertyNamingStrategy(new CamelCaseNamingStrategy());  может не поддерживаться.

abstract class Animal
{
  public String name;
}

class Dog extends Animal
{
  public String breed;
  public String leash_color;
}

class Cat extends Animal
{
  public String favorite_toy;
}

class Bird extends Animal
{
  public String wing_span;
  public String preferred_food;
}