This article provides a robust working approach to creating JSON structures that include a bidirectional relationship without resulting in these errors.
Often, the solutions that are presented to this problem entail workarounds that basically side-step, but don’t really address, the issue. Examples include using Jackson annotation types like
@JsonBackReference (which simply omits the back reference from serialization) or using
@JsonIgnore to simply ignore one of the sides of the relationship. Alternatively, one can develop custom serialization code that ignore any such bidirectional relationship or circular dependency in the data.
But we don’t want to ignore or omit either side of the bidirectional relationship. We want to preserve it, in both directions, without generating any errors. A real solution should allow circular dependencies in JSON and allow the developer to stop thinking about them without taking additional actions to fix them. This article provides a practical and straightforward technique for doing so, which can serve as a useful addition to any standard set of tips and practices for today’s front-end developer.
A Simple Bidirectional Relationship Example
A common case where this bidirectional relationship (a.k.a. circular dependency) issue arises is when there is a parent object that has children (which it references) and those child objects, in turn, want to maintain references to their parent. Here’s a simple example:
If you try to convert the above
parent object to JSON (for example, by using the
stringify method, as in
var parentJson = JSON.stringify(parent);), the exception Uncaught TypeError: Converting circular structure to JSON will be thrown.
While we could use one of the techniques discussed above (such as using annotations like
@JsonIgnore), or we could simply remove the above references to the parent from the children, these are ways of avoiding rather than solving the problem. What we really want is a resulting JSON structure that maintains each bidirectional relationship and that we can convert to JSON without throwing any exceptions.
Moving Toward a Solution
One potentially obvious step toward a solution is to add some form of object ID to each object and then replace the children’s references to the parent object with references to the parent object’s id. For example:
This approach will certainly avoid any exceptions that result from a bidirectional relationship or circular reference. But there’s still an issue, and that issue becomes apparent when we think about how we would go about serializing and deserializing these references.
The issue is that we would need to know, using the above example, that every reference to the value 100 refers to the parent object (since that’s its
the property. But what if we add another property with the value 100? For example:
id). That will work just fine in the above example where the only property that has the value 100 is
If we assume that any reference to the value 100 is referencing an object, there will be no way for our serialization/deserialization code to know that when
object’s , it will incorrectly replace the its value with a reference to the parent object).
parent references the value 100, that IS referencing the parent object’s
id, but when
priority references the value 100, that is NOT referencing the parent object’s
id (and since it will think that
priority is also referencing the parent
You may ask at this point, Wait, you’re missing an obvious solution. Instead of using the property value to determine that it’s referencing an object id, why don’t you just use the property name? Indeed, that is an option, but a very limiting one. It will mean that we will need to predesignate a list of reserved property names that are always assumed to reference other objects (names like parent, child, next, etc.). This will then mean that only those property names can be used for references to other objects and will also mean that those property names will always be treated as references to other objects. This is therefore not a viable alternative in most situations.
So it looks like we need to stick with recognizing property values as object references. But this means that we will need these values to be guaranteed to be unique from all other property values. We can address the need for unique values by using Globally Unique Identifiers (GUIDs). For example:
A Fully Automated Solution
Remember our original challenge. We wanted to be able to serialize and deserialize objects that have a bidirectional relationship to/from JSON without generating any exceptions. While the above solution accomplishes this, it does so by requiring us to (a) add some form of unique ID field to each object and (b) replace each object reference with the corresponding unique ID. This will work, but we’d much prefer a solution that would just automatically work with our existing object references without requiring us to manually modify our objects this way.
Ideally, we want to be able to pass a set of objects (containing any arbitrary set of properties and object references) through the serializer and deserializer (without generating any exceptions based on a bidirectional relationship) and having the objects generated by the deserializer precisely match the objects that were fed into the serializer.
So returning to our example, we want to feed the following set of objects as is to our serializer:
We would then expect the serializer to generate a JSON structure similar to the following:
So now that we know what we want to do and how we want to do it, let’s implement it.
bidirectional relationship without throwing any exceptions.
Passing a set of objects (including those that have a bidirectional relationship) through these two methods is essentially an identity function; i.e.,
convertToObject(convertToJson(obj)) === obj evaluates to true.
Java / Jackson Example
Now let’s look at how this
apporach is supported in popular external libraries. For example, let’s see how it’s handled in Java using the Jackson library.
Let’s see an example:
Consider the following example:
As shown in
the array, we can simply include object references in our JSON rather than replicas of the referenced objects and their content.
With this solution, you can eliminate circular reference related exceptions while serializing JSON files in a way that minimizes any constraints on your objects and data. If no such solution is already available in the libraries you’re using for handling serialization of JSON files, you can implement your own solution based on the example implementation provided. Hope you find this helpful.
This blog is listed under Development & Implementations Community
Share your perspective
Share your achievement or new finding or bring a new tech idea to life. Your IT community is waiting!