问题
I am attempting to work through a tutorial from Programmer Bruce that is supposed to allow the deserialization of polymorphic JSON.
The complete list can be found here Programmer Bruce tutorials (Great stuff btw)
I have worked through the first five with no problems but I have hit a snag on the last one (Example 6), which of course is the one I really need to get working.
I am getting the following error at compile time
The method readValue(JsonParser, Class) in the type ObjectMapper is not applicable for the arguments (ObjectNode, Class)
and it's being caused by the chunk of code
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);
}
}
Specifically by the line
return 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 Thanks in advance Jon D.
回答1:
As promised, I'm putting an example for how to use annotations to serialize/deserialize polymorphic objects, I based this example in the Animal
class from the tutorial you were reading.
First of all your Animal
class with the Json Annotations for the subclasses.
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;
}
}
Then your subclasses, Dog
and 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;
}
As you can see, there is nothing special for Cat
and Dog
, the only one that know about them is the abstract
class Animal
, so when deserializing, you'll target to Animal
and the ObjectMapper
will return the actual instance as you can see in the following test:
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();
}
}
}
Output after running the Test
class:
{"@type":"Dog","name":"ruffus","breed":"english shepherd"}
Deserialized dogJson Class: Dog
{"@type":"Cat","name":"goya","favoriteToy":"mice"}
Deserialized catJson Class: Cat
Hope this helps,
Jose Luis
回答2:
A simple way to enable polymorphic serialization / deserialization via Jackson library is to globally configure the Jackson object mapper (jackson.databind.ObjectMapper) to add information, such as the concrete class type, for certain kinds of classes, such as abstract classes.
To do that, just make sure your mapper is configured correctly. For example:
Option 1: Support polymorphic serialization / deserialization for abstract classes (and Object typed classes)
jacksonObjectMapper.enableDefaultTyping(
ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
Option 2: Support polymorphic serialization / deserialization for abstract classes (and Object typed classes), and arrays of those types.
jacksonObjectMapper.enableDefaultTyping(
ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
Reference: https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization
回答3:
You need only one line before the declaration of the class Animal
for correct polymorphic serialization/deserialization:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public abstract class Animal {
...
}
This line means: add a meta-property on serialization or read a meta-property on deserialization (include = JsonTypeInfo.As.PROPERTY
) called "@class" (property = "@class"
) that holds the fully-qualified Java class name (use = JsonTypeInfo.Id.CLASS
).
So, if you create a JSON directly (without serialization) remember to add the meta-property "@class" with the desired class name for correct deserialization.
More information here
回答4:
If using the fasterxml then,
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;
in main method--
use
SimpleModule module =
new SimpleModule("PolymorphicAnimalDeserializerModule");
instead of
new SimpleModule("PolymorphicAnimalDeserializerModule",
new Version(1, 0, 0, null));
and in Animal deserialize() function, make below changes
//Iterator<Entry<String, JsonNode>> elementsIterator = root.getFields();
Iterator<Entry<String, JsonNode>> elementsIterator = root.fields();
//return mapper.readValue(root, animalClass);
return mapper.convertValue(root, animalClass);
This works for fasterxml.jackson. If it still complains of the class fields. Use the same format as in the json for the field names (with "_" -underscore). as this//mapper.setPropertyNamingStrategy(new CamelCaseNamingStrategy());
might not be supported.
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;
}
来源:https://stackoverflow.com/questions/30362446/deserialize-json-with-jackson-into-polymorphic-types-a-complete-example-is-giv