Resolve circular references from JSON object
The json object which you are receiving from the server contains Circular References. Before using the object you should have to first remove all the $ref
properties from the object, means in place of $ref : "1"
you have to put the object which this link points.
In your case may be it is pointing to the User's object whose id is 1
For this you should check out Douglas Crockfords Plugin on github.There is a cycle.js which can do the job for you.
or you can use the following code (not tested) :
function resolveReferences(json) {
if (typeof json === 'string')
json = JSON.parse(json);
var byid = {}, // all objects by id
refs = []; // references to objects that could not be resolved
json = (function recurse(obj, prop, parent) {
if (typeof obj !== 'object' || !obj) // a primitive value
return obj;
if ("$ref" in obj) { // a reference
var ref = obj.$ref;
if (ref in byid)
return byid[ref];
// else we have to make it lazy:
refs.push([parent, prop, ref]);
return;
} else if ("$id" in obj) {
var id = obj.$id;
delete obj.$id;
if ("$values" in obj) // an array
obj = obj.$values.map(recurse);
else // a plain object
for (var prop in obj)
obj[prop] = recurse(obj[prop], prop, obj)
byid[id] = obj;
}
return obj;
})(json); // run it!
for (var i=0; i<refs.length; i++) { // resolve previously unknown references
var ref = refs[i];
ref[0][ref[1]] = byid[refs[2]];
// Notice that this throws if you put in a reference at top-level
}
return json;
}
Let me know if it helps !
How to restore circular references (e.g. $id) from Json.NET-serialized JSON?
Ok so I created a more robust method which will use $id as well as $ref, because that's actually how json.net handles circular references. Also you have to get your references after the id has been registered otherwise it won't find the object that's been referenced, so I also have to hold the objects that are requesting the reference, along with the property they want to set and the id they are requesting.
This is heavily lodash/underscore based
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['lodash'], factory);
} else {
factory(_);
}
})(function (_) {
var opts = {
refProp: '$ref',
idProp: '$id',
clone: true
};
_.mixin({
relink: function (obj, optsParam) {
var options = optsParam !== undefined ? optsParam : {};
_.defaults(options, _.relink.prototype.opts);
obj = options.clone ? _.clone(obj, true) : obj;
var ids = {};
var refs = [];
function rl(s) {
// we care naught about primitives
if (!_.isObject(s)) {
return s;
}
if (s[options.refProp]) {
return null;
}
if (s[options.idProp] === 0 || s[options.idProp]) {
ids[s[options.idProp]] = s;
}
delete s[options.idProp];
_(s).pairs().each(function (pair) {
if (pair[1]) {
s[pair[0]] = rl(pair[1]);
if (s[pair[0]] === null) {
if (pair[1][options.refProp] !== undefined) {
refs.push({ 'parent': s, 'prop': pair[0], 'ref': pair[1][options.refProp] });
}
}
}
});
return s;
}
var partialLink = rl(obj);
_(refs).each(function (recordedRef) {
recordedRef['parent'][recordedRef['prop']] = ids[recordedRef['ref']] || {};
});
return partialLink;
},
resolve: function (obj, optsParam) {
var options = optsParam !== undefined ? optsParam : {};
_.defaults(options, _.resolve.prototype.opts);
obj = options.clone ? _.clone(obj, true) : obj;
var objs = [{}];
function rs(s) {
// we care naught about primitives
if (!_.isObject(s)) {
return s;
}
var replacementObj = {};
if (objs.indexOf(s) != -1) {
replacementObj[options.refProp] = objs.indexOf(s);
return replacementObj;
}
objs.push(s);
s[options.idProp] = objs.indexOf(s);
_(s).pairs().each(function (pair) {
s[pair[0]] = rs(pair[1]);
});
return s;
}
return rs(obj);
}
});
_(_.resolve.prototype).assign({ opts: opts });
_(_.relink.prototype).assign({ opts: opts });
});
I created a gist here
How to solve circular reference when serializing an object which have a class member with the same type of that object
I think using ExclusionStrategy
is not the right approach to solve this problem.
I would rather suggest to use JsonSerializer
and JsonDeserializer
customized for your StructId
class.
(May be an approach using TypeAdapter
would be even better,
but I didn't have enough Gson experience do get this working.)
So you would create your Gson
instance by:
Gson gson = new GsonBuilder()
.registerTypeAdapter(StructId.class, new StructIdSerializer())
.registerTypeAdapter(StructId.class, new StructIdDeserializer())
.setPrettyPrinting()
.create();
The StructIdSerializer
class below is responsible for converting a StructId
to JSON.
It converts its properties Name
, Type
and ChildId
to JSON.
Note that it does not convert the property ParentId
to JSON,
because doing that would produce infinite recursion.
public class StructIdSerializer implements JsonSerializer<StructId> {
@Override
public JsonElement serialize(StructId src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("Name", src.Name);
jsonObject.add("Type", context.serialize(src.Type));
jsonObject.add("ChildId", context.serialize(src.ChildId)); // recursion!
return jsonObject;
}
}
The StructIdDeserializer
class below is responsible for converting JSON to a StructId
.
It converts the JSON properties Name
, Type
and ChildId
to corresponding Java fields in StructId
.
Note that the ParentId
Java field is reconstructed from the JSON nesting structure,
because it is not directly contained as a JSON property.
public class StructIdDeserializer implements JsonDeserializer<StructId> {
@Override
public StructId deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
StructId id = new StructId();
id.Name = json.getAsJsonObject().get("Name").getAsString();
id.Type = context.deserialize(json.getAsJsonObject().get("Type"), StructType.class);
JsonElement childJson = json.getAsJsonObject().get("ChildId");
if (childJson != null) {
id.ChildId = context.deserialize(childJson, StructId.class); // recursion!
id.ChildId.ParentId = id;
}
return id;
}
}
I tested the code above with this JSON input example
{
"Name": "John",
"Type": "A",
"ChildId": {
"Name": "Jane",
"Type": "B",
"ChildId": {
"Name": "Joe",
"Type": "A"
}
}
}
by deserializing it withStructId root = gson.fromJson(new FileReader("example.json"), StructId.class);
,
then by serializing that withSystem.out.println(gson.toJson(root));
and got the original JSON again.
How to resolve circular reference in JSON serializing?
Take a look at http://wiki.fasterxml.com/JacksonFeatureObjectIdentity
A simple example would be:
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
public class Identifiable
{
public int value;
public Identifiable next;
}
and if we created a cycle consisting of two values, like:
Identifiable ob1 = new Identifiable();
ob1.value = 13;
Identifiable ob2 = new Identifiable();
ob2.value = 42;
// link as a cycle:
ob1.next = ob2;
ob2.next = ob1;
and serialized using:
String json = objectMapper.writeValueAsString(ob1);
we would get following serialization for JSON:
{
"@id" : 1,
"value" : 13,
"next" : {
"@id" : 2,
"value" : 42,
"next" : 1
}
}
Json and Java - Circular Reference
There are two ways you can go about this. 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.
Another way 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.
This addresses your comment: I think what you might want to do in this case is use a DTO that is visible to the outside world. I like this approach because I don't want to expose my entities to the outside. This means that the Jackson annotations would be on the DTO and not on the enity. You would need some sort of mapper or converter that converts the entity to the DTO. Now when you make changes to your entity, they won't get propagated to the DTO unless you modify your mapper/converter. I think this is ok, because when you make a change to your entity you can decide if you want that change to be exposed or not.
UPDATE
There is a good blog post here that goes into detail about the various ways you can handle bidirectional relationships in Jackson. It describes solutions that use @JsonIgnore
, @JsonManagedReference
and @JsonBackReference
, @JsonIdentityInfo
, @JsonView
and a custom serializer as well. It's a pretty comprehensive writeup of the various techniques that you can use.
How to solve circular reference in json serializer caused by hibernate bidirectional mapping?
Can a bi-directional relationship even be represented in JSON? Some data formats are not good fits for some types of data modelling.
One method for dealing with cycles when dealing with traversing object graphs is to keep track of which objects you've seen so far (using identity comparisons), to prevent yourself from traversing down an infinite cycle.
using toJSON() to filter out circular reference from array of objects before writing to .json file
There are two main ways to solve this issue:
One is to use cycle.js which introduces two functions, JSON.decycle()
and JSON.retrocycle()
, which makes it possible to encode and decode cyclical structures and dags into an extended and retrocompatible JSON format.
Or you can implement your own solution which will require finding and replacing (or removing) the cyclic references by serializable values.
The snippet below illustrates how to find and filter (thus causing data loss) a cyclic reference by using the replacer
parameter of JSON.stringify()
:
var circularReference = {otherData: 123};
circularReference.myself = circularReference;
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
JSON.stringify(circularReference, getCircularReplacer());
Please refer here for more information.
Related Topics
Create an Object from an Array of Keys and an Array of Values
How to Get the Browser Language Using JavaScript
How to Call a C# Function from JavaScript
Invert Y Axis of L:Crs.Simple Map on Vue2-Leaflet
How to Split Comma Separated String Using JavaScript
How to Access Constants in the Lib/Constants.Js File in Meteor
Why Does JavaScript's Eval Need Parentheses to Eval JSON Data
How to Add/Remove a Class in JavaScript
Difference Between Date(Datestring) and New Date(Datestring)
Use Browser Search (Ctrl+F) Through a Button in Website
How to Make a Shared State Between Two React Components
Weird JSON JavaScript Problem in Rails
The Mystery of the Disappearing Checkmarks
Understanding Promises in Node.Js
Adding Click Event Handler to Iframe
Is There a Version of JavaScript's String.Indexof() That Allows for Regular Expressions