返回文章页面 Deserialize JSON 进入多态类型译者:

我正在尝试学习一个来自 Programmer Bruce 的教程,该教程允许多态 JSON 的反序列化。

完整的列表可以在这里找到 程序员 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) ;

Has anyone run into this before and if so, was there a solution?

I'd appreciate any help anyone can give 先谢谢你 Jon D.

170559 次浏览

正如所承诺的那样,我将举一个例子来说明如何使用注释来序列化/反序列化多态对象,我将这个例子基于您正在阅读的教程中的 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;


}

正如你所看到的,对于 CatDog没有什么特别的,唯一知道它们的是 abstractAnimal,所以当反序列化时,你将目标对准 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

希望这对你有帮助,

Jose Luis

如果使用 fasterxml,

these changes might be needed

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

instead of

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

并在 Animal 反序列化()函数中进行以下更改

//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 中相同的格式(使用“ _”-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;
}

通过 Jackson 库启用多态序列化/反序列化的一个简单方法是全局配置 Jackson 对象映射器(Jackson.database)。ObjectMapper)为某些类(如抽象类)添加信息,如具体类类型。

To do that, just make sure your mapper is configured correctly. For example:

选项1: 支持抽象类(和对象类型类)的多态序列化/反序列化

jacksonObjectMapper.enableDefaultTyping(
ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);

选项2: 支持抽象类(和对象类型类)以及这些类型的数组的多态序列化/反序列化。

jacksonObjectMapper.enableDefaultTyping(
ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);

Reference: https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization

为了正确的多态序列化/反序列化,在类 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”,并使用所需的类名来进行正确的反序列化。

更多信息 给你

处理多态性要么是模型绑定的,要么需要使用各种自定义反序列化器的大量代码。我是一个 动态反序列化库的合著者,它允许独立于模型的 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());
}
}
}

用于 pretius-jddl 的 Maven 依赖项(请在 Maven.org/jddl检查最新版本:

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

@jbarrueta answer是完美的,在2.12版本的 Jackson 中引入了一个期待已久的 @JsonTypeInfo注释新类型 DEDUCTION

对于无法更改传入的 json 或者不能这样做的情况,它非常有用。我仍然建议使用 use = JsonTypeInfo.Id.NAME,作为在复杂情况下 可能会抛出异常无法确定使用哪个子类型时的新方法。

现在你可以简单地写了

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.DEDUCTION)
@JsonSubTypes({
@JsonSubTypes.Type(Dog.class),
@JsonSubTypes.Type(Cat.class) }
)
public abstract class Animal {


private String name;


public String getName() {
return name;
}


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


}

它将产生 {"name":"ruffus", "breed":"english shepherd"}{"name":"goya", "favoriteToy":"mice"}

同样,如果某些字段可能不存在,比如 breedfavoriteToy,那么使用 NAME会更安全。

如果现有属性的名称不等于 name,则可以使用注释值 EXISTING_PROPERTY

如果属性名是例如 type而不是 name,您可以使用这个注释:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type")

See also https://stackoverflow.com/a/62278471/1909531