43
votes

I want to convert a JSON string into java object, but the class of this object contains abstract fields, which Jackson can't instantiate, and doesn't produce the object. What is the easiest way to tell it about some default implementation of an abstract class, like

setDefault(AbstractAnimal.class, Cat.class);

or to decide about the implementation class based on JSON attribute name, eg. for JSON object:

{
    ...
    cat: {...}
    ...
}

i would just wite:

setImpl("cat", Cat.class);


I know it's possible in Jackson to embed class information inside JSON, but I don't want to complicate the JSON format I use. I want to decide what class to use just by setting default implementation class, or by the attribute name ('cat') - like in XStream library, where you write:

xStream.alias("cat", Cat.class);

Is there a way to do so, especially in one line, or does it require some more code?

4
There is no such thing as an 'abstract field' in Java.user207421
what I meant is: class C { Animal animal; } and I want to instantiate C, where Aniaml is abstract, and I want to put in this field a Cat, which extends AnimalMarcin
so there is no problem. There is no rule against variables being of abstract types.user207421
It is possible to embed class information inside JSON: @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type"). See comments at stackoverflow.com/a/32777371/873282koppor

4 Answers

57
votes

There are multiple ways; before version 1.8, simplest way is probably to do:

@JsonDeserialize(as=Cat.class)
public abstract class AbstractAnimal { ... }

as to deciding based on attribute, that is best done using @JsonTypeInfo, which does automatic embeddeding (when writing) and use of type information.

There are multiple kinds of type info (class name, logical type name), as well as inclusion mechanisms (as-included-property, as-wrapper-array, as-wrapper-object). This page: https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization explains some of the concepts.

4
votes

If you want to pollute neither your JSON with extra fields nor your classes with annotation, you can write a very simple module and deserializer that uses the default subclass you want. It is more than one line due to some boilerplate code, but it is still relatively simple.

class AnimalDeserializer extends StdDeserializer<Animal> {
    public AnimalDeserializer() {
        super(Animal.class);
    }

    public Animal deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
        return jsonParser.readValueAs(Cat.class);
    }
}

class AnimalModule extends SimpleModule {
    {
        addDeserializer(Animal.class, new AnimalDeserializer());
    }
}

Then register this module for the ObjectMapper and that's it (Zoo is the container class that has an Animal field).

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new AnimalModule());
return objectMapper.readValue(json, Zoo.class);
2
votes

A full fledged answer with a very clear example can be found here: https://stackoverflow.com/a/30386694/584947

Jackson refers to this as Polymorphic Deserialization.

It definitely helped me with my issue. I had an abstract class that I was saving in a database and needed to unmarshal it to a concrete instance of a class (understandably).

It will show you how to properly annotate the parent abstract class and how to teach jackson how to pick among the available sub-class candidates at run-time when unmarshaling.

2
votes

the problem can solve with the annotation @JsonDeserialize on the abstract class refers to Jackson Exceptions Problems and Solutions https://www.baeldung.com/jackson-exception