Десериалізувати JSON з Джексоном на поліморфні типи - Повний приклад дає мені помилку компіляції


79

Я намагаюся працювати з підручником програміста Брюса, який повинен дозволити десеріалізацію поліморфного 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);
  }  
} 

Конкретно за рядком

повернути mapper.readValue (root, animalClass);

Хто-небудь стикався з цим раніше, і якщо так, чи було рішення?

Буду вдячний будь-якій допомозі, котра може надати подяку, Джон Д.


Яку версію Джексона ви використовуєте, підручник передбачає Jackson 1.x, а також будь-яку причину, чому не віддати перевагу десеріалізації на основі анотацій для поліморфних екземплярів?
jbarrueta

Я використовую 2.5. Я бачу, чи знизить дану версію до 1.X проблему. Крім того, чи можете ви порекомендувати навчальний посібник / приклад, який може показати використання анотацій для вирішення цієї проблеми?
Джон Дрісколл

Так, я б не рекомендував вам знижувати рейтинг, я з радістю наведу приклад роботи.
jbarrueta

2
Ось ще одна стаття, яка добре пояснює різні способи проведення поліморфної серіалізації / десериалізації
Jerome L

Я щойно додав (можливо) простіше рішення, яке обробляє десериалізацію для різних типів на основі наявності властивості: stackoverflow.com/a/50013090/1030527
Берні,

Відповіді:


134

Як і було обіцяно, я наводжу приклад того, як використовувати анотації для серіалізації / десеріалізації поліморфних об’єктів, я базував цей приклад у Animalкласі з підручника, який ви читали.

Перш за все ваш Animalклас із анотаціями Json для підкласів.

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клас 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

Сподіваюся, це допомагає,

Хосе Луїс


8
що робити, якщо я отримую об’єкт з модернізації без інформації @type?
Ренан Бандейра

7
Чи можна це зробити без використання анотації @JsonSubTypes? Так що підкласи можна додавати в інші пакунки після того, як автор оригінального класу був написаний?
HDзберегти

1
@KamalJoshi так, ви можете зробити те саме, якщо Animal - це інтерфейс. Просто перевірено, змінивши Animal на інтерфейс, і той самий основний метод спрацював. Що стосується Вашого другого питання, то правильно, що назва не пов’язана Dogз анотацією, nameце лише поле серіалізованого класу, мабуть, щоб уникнути плутанини, я міг би використовувати petNameint Animalклас замість цього name. Поле анотації Джексона name- це значення, яке слід встановити для серіалізованого Json як ідентифікатора. HTH.
jbarrueta

4
@HDave Я створив суть, @JsonTypeIdResolverяка (можливо) робить те, що вам потрібно: gist.github.com/root-talis/36355f227ff5bb7a057ff7ad842d37a3
Г. Каштанов,

1
@ user1300959 ні, їм не потрібно реалізовувати, Serializableщоб цей приклад працював.
jbarrueta

10

Вам потрібен лише один рядок перед оголошенням класу 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" з потрібною назвою класу для правильної десеріалізації.

Більше інформації тут


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


1
куди мені це покласти (im using hibernate)?
AndroidTank 24.03.17

У контексті, де ObjectMapper визначений і перед тим, як його використовувати. Наприклад, якщо ви використовуєте статичний ObjectMapper, ви можете додати статичний абзац і встановити там ці значення.
AmitW

1
я не використовую objectmapper. Це просто працює без будь-якого визначення.
AndroidTank

Надане вами посилання, здається, недійсне ... чи не може бути так, що тепер URL-адреса github.com/FasterXML/jackson-docs/wiki/… ?
SJuan76,

Здається, надане посилання SJuan76 працює зараз, однак посилання, яке ви надали, містить ту саму інформацію.
AmitW

0

Обробка поліморфізму або пов'язана з моделлю, або вимагає багато коду з різними спеціальними десериалізаторами. Я співавтор бібліотеки динамічної десеріалізації JSON, яка дозволяє створювати незалежну від моделі бібліотеку десериалізації json. Рішення проблеми OP можна знайти нижче. Зверніть увагу, що правила оголошуються дуже коротко.

public class SOAnswer {
    @ToString @Getter @Setter
    @AllArgsConstructor @NoArgsConstructor
    public static abstract class Animal {
        private String name;    
    }

    @ToString(callSuper = true) @Getter @Setter
    @AllArgsConstructor @NoArgsConstructor
    public static class Dog extends Animal {
        private String breed;
    }

    @ToString(callSuper = true) @Getter @Setter
    @AllArgsConstructor @NoArgsConstructor
    public static class Cat extends Animal {
        private String favoriteToy;
    }
    
    
    public static void main(String[] args) {
        String json = "[{"
                + "    \"name\": \"pluto\","
                + "    \"breed\": \"dalmatian\""
                + "},{"
                + "    \"name\": \"whiskers\","
                + "    \"favoriteToy\": \"mouse\""
                + "}]";
        
        // create a deserializer instance
        DynamicObjectDeserializer deserializer = new DynamicObjectDeserializer();
        
        // runtime-configure deserialization rules; 
        // condition is bound to the existence of a field, but it could be any Predicate
        deserializer.addRule(DeserializationRuleFactory.newRule(1, 
                (e) -> e.getJsonNode().has("breed"),
                DeserializationActionFactory.objectToType(Dog.class)));
        
        deserializer.addRule(DeserializationRuleFactory.newRule(1, 
                (e) -> e.getJsonNode().has("favoriteToy"),
                DeserializationActionFactory.objectToType(Cat.class)));
        
        List<Animal> deserializedAnimals = deserializer.deserializeArray(json, Animal.class);
        
        for (Animal animal : deserializedAnimals) {
            System.out.println("Deserialized Animal Class: " + animal.getClass().getSimpleName()+";\t value: "+animal.toString());
        }
    }
}

Залежність Maven від pretius-jddl (перевірити найновішу версію на maven.org/jddl :

<dependency>
  <groupId>com.pretius</groupId>
  <artifactId>jddl</artifactId>
  <version>1.0.0</version>
</dependency>

-1

Якщо ви використовуєте 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));

а у функції Animal 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, для імен полів (з підкресленням "_"). оскільки це
//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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.