Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursive objects to JSON

Suppose I have the following code:

public class A {
    B b;

    public A() {
        this.b = new B(this);
    }
}

public class B {
    A a;

    B(A a) {
        this.a = a;
    }

}

As you can see, those objects (A and B) are referring to eachother, causing an infinite recursion if you try to convert the objects to JSON code: A has B, which as the same A, which has the same B, et cetera, et cetera.

But if I try to convert those objects to JSON, for example using Gson, a StackOverflowError raises, due to the recursion (which is, by the way, completely logical).

Now is there a way in JSON to declare a recursion like this? If not, then is there a way to handle such recursion in JSON?

Or do I have to manually check for recursion, remove it, convert the object to JSON, and reapply the recursion while rebuilding the JSON string to Java objects?

like image 316
MC Emperor Avatar asked Sep 15 '14 11:09

MC Emperor


People also ask

What is nested JSON?

Objects can be nested inside other objects. Each nested object must have a unique access path. The same field name can occur in nested objects in the same document. However, the full access name must still be unique.

Why use recursion Over iteration?

Using recursion in the divide and conquer method can minimize the size of your problem at each step and take less time than a naive iterative approach. It is frequently more 'elegant' to use recursion than iterative solutions because it is easier to implement.


2 Answers

Jackson has some ways to deal with cyclic or bidirectional dependencies, two in particular.

If you must expose your entity to the outside world, I recommend adding @JsonIgnore on the property that is causing the circular reference. This will tell Jackson not to serialize that property. However, that means that you have to reference them yourself again upon deserialization (and on serialization, you have to add something to the JSON datastructure to know what to reference again upon deserialization).

Another way, and I suspect this is more to your liking, is to use the bidirectional features provided by Jackson. You can either use @JsonManagedReference or @JsonBackReference. @JsonManagedReference is the "forward" part of the property and it will get serialized normally. @JsonBackReference is the "back" part of the reference; it will not be serialized, but will be reconstructed when the "forward" type is deserialized.

You can check out the examples here.

Below I'll show one of those examples that should clarify a few things

public class NodeList {
    @JsonManagedReference
    public List<NodeForList> nodes; //this one gets serialized
}

public class NodeForList {
    public String name;

    @JsonBackReference 
    public NodeList parent; //this one wont get serialized, but will be reconstructed upon deserialization

    public NodeForList() { this(null); }
    public NodeForList(String n) { name = n; }
}
like image 132
stealthjong Avatar answered Oct 22 '22 00:10

stealthjong


Although JSON is very similar to JavaScript's literal object notation, they are not quite the same thing. This is a case where the difference matters.

JavaScript has a notion of a reference in memory: many variables can point to the same object. In fact, for some data types, this is baked directly into the type itself: all variables of the same value, if that value is one of these types, point to the same spot. String and Boolean work this way. Technically, so do Null and undefined, though the point is moot since there is only one possible value for each of these types anyway.

Even when references aren't baked into the types, though, it's still possible for variables to refer to the same object in memory.

JSON, by contrast, has no notion of references. Everything is a new and distinct value, and no two things can point to the same place. JSON parsers paper over this difference, as does JavaScript's eval (though you shouldn't be using eval with JSON anyway); as each value is read into memory, it's converted to a variable of the appropriate type, and if that's a type where all variables of the same value point to the same spot, then they do.

But this only works when references are baked into the type; JSON doesn't have a way to specify variables, and so any points in a JSON file that can refer to different spots, do refer to different spots. This means that you can't perform recursion in JSON files.

Instead, just as you say, your JSON generator needs to check for recursion and remove it, replacing it with some kind of note that this value should refer to something else. How you encode those notes is up to you; there are many ways to do it, and some will work better for your application's specific needs than others will. In any event, however you do it, your JSON parser on the other end needs to look for these notes and transform them back into recursive references.

The alternative is to use some other format that has some method of natively encoding references.

like image 29
The Spooniest Avatar answered Oct 21 '22 22:10

The Spooniest