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?
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.
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.
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; }
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With