48

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?

Federico Zancan
  • 4,846
  • 4
  • 44
  • 60
Marcin
  • 825
  • 2
  • 8
  • 12
  • There is no such thing as an 'abstract field' in Java. – user207421 Mar 30 '11 at 23:15
  • 1
    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 Animal – Marcin Mar 31 '11 at 07:07
  • so there is no problem. There is no rule against variables being of abstract types. – user207421 Apr 05 '11 at 10:47
  • It **is** possible to embed class information inside JSON: `@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type")`. See comments at https://stackoverflow.com/a/32777371/873282 – koppor Jun 27 '17 at 10:55

4 Answers4

64

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.

Philipp Nowak
  • 130
  • 11
StaxMan
  • 113,358
  • 34
  • 211
  • 239
  • Thanks for the answer! @JsonDeserialize is what will be ideal to my problem, as it doesn't require embedding any type info in the JSON. However I can't use this, as I cannot modify the classes that I want to instantiate, as they're part of other projects, and changing this code could break other code. – Marcin Apr 06 '11 at 13:39
  • 8
    The annotations are also quite inflexible, as I can't dynamically decide what implementation I use. – Marcin Apr 06 '11 at 13:47
  • 1
    Wrt modifying classes, this is what Jackson mix-in annotations are for. But correct, this annotation is only intended to be used for cases where there is just one implementation type. Jackson 1.8 will have functionality to allow simple abstract type -> impl type mappings, for what that's worth. – StaxMan Apr 08 '11 at 01:09
  • 2
    mix in annotations did the work, thanks for pointing to right direction – Marcin Apr 13 '11 at 12:22
  • 1
    This allows to specify only one class? What when there are 10 of them? – Danubian Sailor Jan 31 '12 at 08:47
  • Then you want to use polymorphic deserialization, usually via `@JsonTypeInfo` – StaxMan Jan 31 '12 at 18:02
  • 1
    The linked wiki is down - new URL seems to be https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization – Philipp Nowak Aug 11 '16 at 08:56
5

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.

fIwJlxSzApHEZIl
  • 11,861
  • 6
  • 62
  • 71
5

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);
István
  • 69
  • 2
  • 5
  • Thanks for this. saved my day. I had multiple implementatation of the abstract class so I had to put a switch case in the custom deserializer. – आनंद Jun 08 '21 at 13:00
  • The boilerplate for module can be reduced as follows: `objectMapper.registerModule(new SimpleModule().addDeserializer(Animal.class, new AnimalDeserializer()));` – Alessandro S. Aug 20 '21 at 17:02
3

The problem can be solved with the annotation @JsonDeserialize on the abstract class. Refers to Jackson Exceptions Problems and Solutions for more info

Alex Vergara
  • 1,766
  • 1
  • 10
  • 29
zedtimi
  • 306
  • 1
  • 6