Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson Deserialize with Subclasses

Tags:

java

json

jackson

Ok, I know there are a bunch of similar questions, but nothing seems to work.

I have the following structure set up for my entities.

public abstract class MyAbstractClass {
   // bunch of properties, getters, and setters that subclasses share

   public abstract String getType();
}

public class MySubclass1 extends MyAbstractClass {
    //  a few unique properties, getters, and setters

    public String getType() {
        return "Type_1"; //always the same for each instance of MySubclass1
    }
}

public class MySubclass2 extends MyAbstractClass {
    //  a few unique properties, getters, and setters

    public String getType() {
        return "Type_2"; //always the same for each instance of MySubclass2
    }
}

In my controller, I try to map a request to the following method.

public @RequestBody MyAbstractClass saveObject(@RequestBody MyAbstractClass mac) {
    // call model to save object
}

I would like to use 1 controller method versus separate ones for the 2 entities. But using the above results in the following.

com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of path.to.my.entity.MyAbstractClass, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information

Makes sense.

TRY 1

@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="implementingClass")
public abstract class MyAbstractClass

What I think it does - adds a metadata implementingClass property that will store the subclass class.

What the result is.

Unexpected token (END_OBJECT), expected FIELD_NAME: missing property 'implementingClass' that is to contain type id  (for class path.to.my.entity.MyAbstractClass)

Tried with "class" instead of "implementingClass" for the property and got similar results.

TRY 2

@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.WRAPPER_OBJECT)
@JsonSubTypes({
    @Type(name="MySubclass1", value=MySubclass1.class),
    @Type(name="MySubclass2", value=MySubclass2.class)
})
public abstract class MyAbstractClass

What I think it does - uses the defined name to do some sort of wrapping thing.

What the result is.

Could not resolve type id 'myUuid' into a subtype of [simple type, class path.to.my.entity.MyAbstractClass]

Same results even when adding @JsonTypeName("MySubclass1") and @JsonTypeName("MySubclass2") to the 2 subclasses.

Other Tries

I tried a lot. Nothing works. Won't include everything here.

I feel like there should be a simple way to do this, but I just keep on configuring things incorrectly.

I feel like the getType could maybe be leveraged, but I don't want to add an actual property for type (it's just a helper method). Also I would like to do this with annotations versus other options.

Thank you.

like image 568
Snowy Coder Girl Avatar asked Oct 14 '15 20:10

Snowy Coder Girl


People also ask

Does Jackson need no arg constructor?

Jackson won't use a constructor with arguments by default, you'd need to tell it to do so with the @JsonCreator annotation. By default it tries to use the no-args constructor which isn't present in your class.

Can Jackson ObjectMapper be reused?

ObjectMapper is the most important class in Jackson API that provides readValue() and writeValue() methods to transform JSON to Java Object and Java Object to JSON. ObjectMapper class can be reused and we can initialize it once as Singleton object.

How does Jackson deserialization work?

Jackson uses default (no argument) constructor to create object and then sets value using setters. so you only need @NoArgsConstructor and @Setter.

Can Jackson serialize interface?

Jackson can serialize and deserialize polymorphic data structures very easily. The CarTransporter can itself carry another CarTransporter as a vehicle: that's where the tree structure is! Now, you know how to configure Jackson to serialize and deserialize objects being represented by their interface.


1 Answers

I figured it out but I guess I'll answer in case anyone else has this problem.

I added a type property to my subclasses instead of just a helper method (one example included below).

public class MySubclass1 extends MyAbstractClass {
    @Transient
    private final String type = "TYPE_1";

    public String getType() {
        return type;
    }
}

Then I did the following for my abstract superclass.

@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
@JsonSubTypes({
    @Type(name="TYPE_1", value=MySubclass1.class),
    @Type(name="TYPE_2", value=MySubclass2.class)
})
public abstract class MyAbstractClass

When providing the JSON, I was sure to include the type. I won't include this because it's weird knockout insanity.

It's not great. But it worked.

like image 66
Snowy Coder Girl Avatar answered Sep 28 '22 03:09

Snowy Coder Girl