使用 Jackson 将 JSON 反序列化为多态类型 - 一个完整的示例给我一个编译错误
Deserialize JSON with Jackson into Polymorphic Types - A Complete Example is giving me a compile error
我正在尝试完成程序员 Bruce 的教程,该教程应该允许多态 JSON 的反序列化。
完整列表可在此处找到
Programmer Bruce tutorials(很棒的东西顺便说一句)
我已经完成了前五个,没有任何问题,但我在最后一个(示例 6)上遇到了障碍,这当然是我真正需要开始工作的那个。
我在编译时遇到以下错误
The method readValue(JsonParser, Class) in the type ObjectMapper is not applicable for the arguments (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);
有没有人运行以前参与过这个,如果有,有没有解决方案?
如果有人能提供任何帮助,我将不胜感激
提前致谢
乔恩 D.
正如所承诺的那样,我正在举一个例子来说明如何对 serialize/deserialize 多态对象使用注解,这个例子是基于你正在阅读的教程中的 Animal
class .
首先你的 Animal
class 带有 Json 子class注释。
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;
}
}
然后是你的子class,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
classAnimal
,因此在反序列化时,您将以 Animal
为目标,而 ObjectMapper
将 return 实际实例,如您在以下测试中所见:
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
class 之后的输出:
{"@type":"Dog","name":"ruffus","breed":"english shepherd"}
Deserialized dogJson Class: Dog
{"@type":"Cat","name":"goya","favoriteToy":"mice"}
Deserialized catJson Class: Cat
希望这对您有所帮助,
何塞·路易斯
如果使用 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);
这适用于 fasterxml.jackson。如果它仍然抱怨 class 字段。对字段名称使用与 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;
}
通过 Jackson 库启用多态序列化/反序列化的一种简单方法是全局配置 Jackson 对象映射器 (jackson.databind.ObjectMapper) 以添加信息,例如特定类型的具体 class 类型classes,例如摘要 classes。
为此,只需确保您的映射器配置正确。
例如:
选项 1:支持抽象 classes(和对象类型 classes)的多态序列化/反序列化
jacksonObjectMapper.enableDefaultTyping(
ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
选项 2:支持抽象 classes(和对象类型 classes)以及这些类型的数组的多态序列化/反序列化。
jacksonObjectMapper.enableDefaultTyping(
ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
参考:https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization
你只需要在 class Animal
的声明前一行来获得正确的多态 serialization/deserialization:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public abstract class Animal {
...
}
这一行的意思是:在序列化时添加一个meta-属性,或者在反序列化(include = JsonTypeInfo.As.PROPERTY
)中读取一个meta-属性,名为“@class”(property = "@class"
) 包含完全限定的 Java class 名称 (use = JsonTypeInfo.Id.CLASS
).
因此,如果您直接创建 JSON(没有序列化),请记住添加具有所需 class 名称的元 属性“@class”正确的反序列化。
更多信息here
处理多态性要么是模型绑定的,要么需要大量带有各种自定义反序列化器的代码。我是 JSON Dynamic Deserialization Library 的合著者,它允许独立于模型的 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 是完美的,在 Jackson 的 2.12 版本中引入了一个新的期待已久的 @JsonTypeInfo
注释类型,DEDUCTION
.
当您无法更改传入的 json 或不能更改时,它很有用。我仍然建议在复杂情况下使用 use = JsonTypeInfo.Id.NAME
作为新方法 may throw an exception,因为它无法确定要使用的子类型。
现在你可以简单地写
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"}
再次重申,如果某些字段可能不存在,如 breed
或 favoriteToy
。
,则使用 NAME
更安全
如果已有的名字属性不等于name
,可以使用注解值EXISTING_PROPERTY
如果 属性 名称是 type
而不是 name
,则可以使用此注释:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type")
另见
我正在尝试完成程序员 Bruce 的教程,该教程应该允许多态 JSON 的反序列化。
完整列表可在此处找到 Programmer Bruce tutorials(很棒的东西顺便说一句)
我已经完成了前五个,没有任何问题,但我在最后一个(示例 6)上遇到了障碍,这当然是我真正需要开始工作的那个。
我在编译时遇到以下错误
The method readValue(JsonParser, Class) in the type ObjectMapper is not applicable for the arguments (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);
有没有人运行以前参与过这个,如果有,有没有解决方案?
如果有人能提供任何帮助,我将不胜感激 提前致谢 乔恩 D.
正如所承诺的那样,我正在举一个例子来说明如何对 serialize/deserialize 多态对象使用注解,这个例子是基于你正在阅读的教程中的 Animal
class .
首先你的 Animal
class 带有 Json 子class注释。
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;
}
}
然后是你的子class,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
classAnimal
,因此在反序列化时,您将以 Animal
为目标,而 ObjectMapper
将 return 实际实例,如您在以下测试中所见:
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
class 之后的输出:
{"@type":"Dog","name":"ruffus","breed":"english shepherd"}
Deserialized dogJson Class: Dog
{"@type":"Cat","name":"goya","favoriteToy":"mice"}
Deserialized catJson Class: Cat
希望这对您有所帮助,
何塞·路易斯
如果使用 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);
这适用于 fasterxml.jackson。如果它仍然抱怨 class 字段。对字段名称使用与 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;
}
通过 Jackson 库启用多态序列化/反序列化的一种简单方法是全局配置 Jackson 对象映射器 (jackson.databind.ObjectMapper) 以添加信息,例如特定类型的具体 class 类型classes,例如摘要 classes。
为此,只需确保您的映射器配置正确。 例如:
选项 1:支持抽象 classes(和对象类型 classes)的多态序列化/反序列化
jacksonObjectMapper.enableDefaultTyping(
ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
选项 2:支持抽象 classes(和对象类型 classes)以及这些类型的数组的多态序列化/反序列化。
jacksonObjectMapper.enableDefaultTyping(
ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
参考:https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization
你只需要在 class Animal
的声明前一行来获得正确的多态 serialization/deserialization:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public abstract class Animal {
...
}
这一行的意思是:在序列化时添加一个meta-属性,或者在反序列化(include = JsonTypeInfo.As.PROPERTY
)中读取一个meta-属性,名为“@class”(property = "@class"
) 包含完全限定的 Java class 名称 (use = JsonTypeInfo.Id.CLASS
).
因此,如果您直接创建 JSON(没有序列化),请记住添加具有所需 class 名称的元 属性“@class”正确的反序列化。
更多信息here
处理多态性要么是模型绑定的,要么需要大量带有各种自定义反序列化器的代码。我是 JSON Dynamic Deserialization Library 的合著者,它允许独立于模型的 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 @JsonTypeInfo
注释类型,DEDUCTION
.
当您无法更改传入的 json 或不能更改时,它很有用。我仍然建议在复杂情况下使用 use = JsonTypeInfo.Id.NAME
作为新方法 may throw an exception,因为它无法确定要使用的子类型。
现在你可以简单地写
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"}
再次重申,如果某些字段可能不存在,如 breed
或 favoriteToy
。
NAME
更安全
如果已有的名字属性不等于name
,可以使用注解值EXISTING_PROPERTY
如果 属性 名称是 type
而不是 name
,则可以使用此注释:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type")
另见