Java object serialization and conversion to JSON

1.1k views Asked by At

I have a Java object. It has numerous fields that have references to other different kinds of Java objects and sometimes, to itself. This object can be best described as a map (or graph) with bi-directional references (or cycles). I am not authorized to analyze its structure, but in order to solve a problem, I have to serialize this graph and store it in a JSON String.

The fact that I can't really have a look at the structure of the object, using library classes is my only option (as far as I think). I have tried json-io, json-lib, google genson, gson and flexjson. But all of these libraries either get stuck and throw an exception due to the presence of a cycle or are able to return a json (only json-io does this) but with a lot of important fields skipped(the ones that are lazy-loaded, and need getters).

Question: Is there something that exists that I might be missing while testing the above listed libraries just to address my problem?

The Java object I have is really convoluted but I don't expect it to be as convoluted as objects that might be used by big websites like facebook. What are some key libraries and their specific configuration that can be used to address my problem?

1

There are 1 answers

4
Sharon Ben Asher On

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:

  1. 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.
  2. 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.
  3. 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"}}