MyPage is a personalized page based on your interests.The page is customized to help you to find content that matters you the most.


I'm not curious

Bidirectional Relationship Support in JSON

Published on 13 February 17
0
1
BY NIRMEL MURTIC - SOFTWARE ENGINEER @ TOPTAL
Ever tried to create a JSON data structure that includes entities that have a bidirectional relationship (i.e., circular reference)? If you have, you’ve likely seen a JavaScript error along the lines of Uncaught TypeError: Converting circular structure to JSON. Or if you’re a Java developer who uses Jackson library, you may have encountered Could not write JSON: Infinite recursion (StackOverflowError) with root cause java.lang.StackOverflowError.
Bidirectional Relationship Support in JSON - Image 1
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 @JsonManagedReference and @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:
Bidirectional Relationship Support in JSON - Image 2
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:
Bidirectional Relationship Support in JSON - Image 3
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 id). That will work just fine in the above example where the only property that has the value 100 is the parent property. But what if we add another property with the value 100? For example:
1
Bidirectional Relationship Support in JSON - Image 4
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 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 object’s id, it will incorrectly replace the its value with a reference to the parent object).
2
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:
Bidirectional Relationship Support in JSON - Image 5

So that should work, right?

Yes.

But…
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.

Our approach is to have our serializer automatically create and add a unique ID (using a GUID) to each object. It then replaces any object reference with that object’s GUID. (Note that the serializer will need to use some unique property name for these IDs as well; in our example, we use @id since presumably prepending the @ to the property name is adequate to ensure that it is unique.) The deserializer will then replace any GUID that corresponds to an object ID with a reference to that object (note that the deserializer will also remove the serializer-generated GUIDs from the deserialized objects, thereby returning them precisely to their initial state).

So returning to our example, we want to feed the following set of objects as is to our serializer:
1
Bidirectional Relationship Support in JSON - Image 6
We would then expect the serializer to generate a JSON structure similar to the following:
Bidirectional Relationship Support in JSON - Image 7

Then feeding the above JSON to the deserializer would generate the original set of objects (i.e., the parent object and its two children, referencing one another properly).

So now that we know what we want to do and how we want to do it, let’s implement it.
Implementing the Serializer in JavaScript
Below is a sample working JavaScript implementation of a serializer that will properly handle a bidirectional relationship without throwing any exceptions.
1
Bidirectional Relationship Support in JSON - Image 8
Bidirectional Relationship Support in JSON - Image 9
Implementing the Deserializer in JavaScript
Below is a sample working JavaScript implementation of a deserializer that will properly handle a bidirectional relationship without throwing any exceptions.
1
Bidirectional Relationship Support in JSON - Image 10
Bidirectional Relationship Support in JSON - Image 11
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.
1
Bidirectional Relationship Support in JSON - Image 12

These two java classes Parent and Child represent the same structure as in JavaScript example in the beginning of this article. The main point here is in using @JsonIdentityInfo annotation which will tell Jackson how to serialize/deserialize these objects.

Let’s see an example:
1
Bidirectional Relationship Support in JSON - Image 13
As result of serializing the parent instance to JSON, the same JSON structure will be returned as in the JavaScript example.
Bidirectional Relationship Support in JSON - Image 14
Another Advantage

The described approach to handling a bidirectional relationship in JSON can also be leveraged to help reduce the size of a JSON file, since it enables you to reference objects simply by their unique ID, rather than needing to include redundant copies of the same object.

Consider the following example:
Bidirectional Relationship Support in JSON - Image 15
As shown in the filteredChildren array, we can simply include object references in our JSON rather than replicas of the referenced objects and their content.
1
Wrap-up
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

Post a Comment

Please notify me the replies via email.

Important:
  • We hope the conversations that take place on MyTechLogy.com will be constructive and thought-provoking.
  • To ensure the quality of the discussion, our moderators may review/edit the comments for clarity and relevance.
  • Comments that are promotional, mean-spirited, or off-topic may be deleted per the moderators' judgment.
You may also be interested in
 
Awards & Accolades for MyTechLogy
Winner of
REDHERRING
Top 100 Asia
Finalist at SiTF Awards 2014 under the category Best Social & Community Product
Finalist at HR Vendor of the Year 2015 Awards under the category Best Learning Management System
Finalist at HR Vendor of the Year 2015 Awards under the category Best Talent Management Software
Hidden Image Url

Back to Top