Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deserialize JSON having references to abstract types in Jackson

Tags:

jackson

I have an issue with object references to abstract classes and JSON serialization and deserialization. The abstracted issue looks like this:

I have a graph consisting of nodes and edges. Each edge connects two nodes. Nodes can be of flavor red and green. Therefore, there is an abstract class Node and two derived classes RedNode and GreenNode. A Node takes an id (@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")):

@JsonSubTypes({
    @JsonSubTypes.Type(value = GreenNode.class, name = "GreenNode"),
    @JsonSubTypes.Type(value = RedNode.class, name = "RedNode")
})
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public abstract class Node {
    public String id;
}

public class RedNode extends Node {
    // ...
}

public class GreenNode extends Node {
    // ...
}

An Edge has a source and a target of type Node, which are serialized as references (@JsonIdentityReference(alwaysAsId = true)):

public class Edge {
    @JsonIdentityReference(alwaysAsId = true)
    public Node source;
    @JsonIdentityReference(alwaysAsId = true)
    public Node target;
}

The graph is defined as follows:

public class Graph {
    public List<GreenNode> greenNodes = new ArrayList();
    public List<RedNode> redNodes = new ArrayList();
    public List<Edge> edges = new ArrayList();
}

An example JSON looks as follows:

{
  "greenNodes" : [ {
    "id" : "g",
    "content" : "green g",
    "greenProperty" : "green"
  } ],
  "redNodes" : [ {
    "id" : "r",
    "content" : "red r",
    "redProperty" : "red"    
  } ],
  "edges" : [ {
    "source" : "g",
    "target" : "r"
  } ]
}

Using an ObjectMapper cannot read this:

Can not construct instance of com.github.koppor.jsonidentityissue.model.Node: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information

The error location is "line: 13, column: 16". Thus, it is hit at the id of the edge. The nodes themselves are properly serialized.

A workaround is to add type information in the json:

@JsonTypeInfo(
      use = JsonTypeInfo.Id.NAME,
      include = JsonTypeInfo.As.PROPERTY,
      property = "type")
public abstract class Node {

Then, everything works:

{
  "greenNodes" : [ {
    "id" : "g",
    "type" : "GreenNode",
    "content" : "green g",
    "greenProperty" : "green"
  } ],
  "redNodes" : [ {
    "id" : "r",
    "type" : "RedNode",
    "content" : "red r",
    "redProperty" : "red"    
  } ],
  "edges" : [ {
    "source" : "g",
    "target" : "r"
  } ]
}

Then, everything works.

Is it really necessary to include type information in the referenced objects to have the reference working? Without the type information, a graph with red and green nodes (and no edges) can be loaded. After an edge comes in, it can't. However, the JSON of an edge contains an id only. The referenced objects are already parsed.

I really like to get rid off the @JsonTypeInfo annotation. Is there a way to have a clean JSON?

The full example is made available at https://github.com/koppor/jackson-jsonidentityreference-issue/tree/issue.

like image 926
koppor Avatar asked Oct 18 '22 10:10

koppor


1 Answers

The current solution is to include fake type information. Full code at https://github.com/koppor/jackson-jsonidentityreference-issue.

The Node gets an existing property type, which is not written:

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.EXISTING_PROPERTY,
    property = "type")
public abstract class Node {
    @JsonIgnore
    public abstract String getType();
}

Each subclass specifies itself as defaultImpl and provides an implementation of getType:

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.EXISTING_PROPERTY,
    property = "type",
    defaultImpl=GreenNode.class)
public class GreenNode extends Node {
    @Override
    public String getType() {
        return "GreeNode";
    }
}

This way, the JSON remains clean, but Jackson can resolve the id reference without any issues.

like image 129
koppor Avatar answered Oct 21 '22 04:10

koppor