用Jackson将反序列化的JSON反序列化为多态类型-一个完整的示例给我一个编译错误


77

我正在尝试浏览Programmer Bruce的教程,该教程应该允许多态JSON的反序列化。

完整列表可在此处找到 Programmer Bruce教程(很棒的东西)

我已经完成了前五项工作,没有任何问题,但是我在最后一项中遇到了麻烦(示例6),这当然是我真正需要工作的部分。

编译时出现以下错误

ObjectMapper类型的方法readValue(JsonParser,Class)不适用于参数(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版本,本教程假定使用Jackson 1.x,还有任何理由为什么不希望对多态实例使用基于注释的反序列化?
jbarrueta 2015年

我正在使用2.5。我可以看到降级到1.X是否可以解决问题。另外,您是否可以推荐一个教程/示例,其中可能显示使用批注来处理此问题?
乔恩·德里斯科尔

是的,我不建议您降级,我会很高兴地举一个例子。
jbarrueta 2015年

2
这是另一篇很好地解释执行多态序列化/反序列化的不同方法的文章:octoperf.com/blog/2018/02/01/polymorphism-with-jackson
Jerome L

我刚刚添加了一个(可以说)更简单的解决方案,该解决方案基于存在属性来处理反序列化为不同类型的问题:stackoverflow.com/a/50013090/1030527
bernie

Answers:


133

如所承诺的,我将举一个如何使用注释对多态对象进行序列化/反序列化的示例,该示例基于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;
    }

}

然后是您的子类DogCat

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;

}

如您所见,Catand并没有什么特别的Dog,唯一了解它们的是abstractclass 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

希望这可以帮助,

何塞·路易斯


7
如果我没有@type信息就从Retrofit获得对象怎么办?
Renan Bandeira

7
是否可以在不使用@JsonSubTypes注释的情况下执行此操作?这样在创建原始类之后,可以将子类添加到其他包中吗?
HDave

1
@KamalJoshi是的,如果Animal是接口,则可以执行相同的操作。通过将Animal更改为接口进行测试,并且使用了相同的主要方法。关于您的第二个问题,名称与Dog注释中的无关,这是正确的,name只是序列化类的一个字段,为避免混淆,我可以使用petNameintAnimal类代替name。jackson注释name字段是要设置为序列化的Json作为标识符的值。HTH。
jbarrueta 2016年

4
@HDave我已经设置了要点@JsonTypeIdResolver(可能)满足您的要点:gist.github.com/root-talis/36355f227ff5bb7a057ff7ad842d37a3
G. Kashtanov

1
@ user1300959不,他们不需要实施Serializable才能使此示例正常工作。
jbarrueta

8

在类的声明之前,只需要一行就可以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库启用多态序列化/反序列化的一种简单方法是全局配置Jackson对象映射器(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
我应该放在哪里(我使用休眠模式)?
AndroidTank

在定义了ObjectMapper且使用前的上下文中。例如,如果使用的是静态ObjectMapper,则可以添加静态段落并在其中设置这些值。
AmitW '17

1
我没有使用objectmapper。它只是工作而没有任何定义。
AndroidTank

您提供的链接似乎已损坏……可能是因为现在的URL是github.com/FasterXML/jackson-docs/wiki/…吗?
SJuan76 '17

SJuan76提供的链接似乎正在运行,但是您提供的链接包含相同的信息。
AmitW

-1

如果使用fasterxml,

可能需要这些更改

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); 

这适用于fastxml.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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.