I think the best option for you is Jackson Streaming API. It allows you to serailaze a POJO into json while teraversing the Object graph, thus maintaining control over the serailization process and you can detect and handle circular references and any other special cases.
EDIT:
I tried to implement an example that handles circular references but i could not complete it. Here are my intermediate findings:
- Calling the default
ObjectMapper.WriteValue(...)
results in the following Exception com.fasterxml.jackson.databind.JsonMappingException: Direct self-reference leading to cycle
which means Jackson can detect cases of self referencing. The default derializer does not know how to handle this case.
- The default behavior of throwing exception can be turned off by specifying
mapper.configure(SerializationFeature.FAIL_ON_SELF_REFERENCES, false);
when this is set, The default derializer will cause stack overflow. so we need to implement a custom serializer which detects and handles the self referencing.
- I tried to write a custom serializer that does the following:
a) detect self referencing. b) if found, print something to mark the reference. c) if not, do the default serialization. However, I could not see how to call "default serialization". My intermediate solution is listed below:
An example class that can have self reference:
public class Node {
public String name = "";
public Node child = null;
public Node(String name) { this.name = name; }
public String getname() { return name; }
public Node getChild() { return child; }
public void setChild(Node n) { child = n; }
}
the custom serializer
public class NodeJsonSerializer extends JsonSerializer<Node> {
// list so we can detect multiple cases of self references
static List<Node> alreadySerializedNodes = new ArrayList<>();
@Override
public void serialize(Node n, JsonGenerator gen, SerializerProvider serializers)
throws IOException, JsonProcessingException {
for (Node alreadySerialized : alreadySerializedNodes) {
// do not invoke equals() since you want to find duplicate references
if (alreadySerialized == n) {
// mark this as self reference
gen.writeStartObject();
gen.writeStringField("node-ref", alreadySerialized.getname());
gen.writeEndObject();
return;
}
}
alreadySerializedNodes.add(n);
// default derialization ...
gen.writeStartObject();
gen.writeStringField("name", n.getname());
gen.writeObjectField("child", n.getChild());
gen.writeEndObject();
}
}
Calling:
Node n1 = new Node("n1");
n1.setChild(n1); // self referencing
ObjectMapper mapper = new ObjectMapper();
// registering custom serializer is through module
SimpleModule sm = new SimpleModule("MyModule");
sm.addSerializer(Node.class, new NodeJsonSerializer());
// making sure default serializer ignores self referencing is through module mapper.configure(SerializationFeature.FAIL_ON_SELF_REFERENCES, false);
mapper.registerModule(sm);
System.out.println(mapper.writeValueAsString(n1));
output is {"name":"n1","child":{"node-ref":"n1"}}