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.
Circular reference issue with JSON-B
In either Password
or Folder
, you need to remove their reference (in serialisation) to each other. You can use @JsonbTransient
to the field you don't want to be serialised in JSON.
(docs: http://json-b.net/docs/user-guide.html#ignoring-properties)
Example: Password
class approach
@JsonbTransient
private Folder mFolder;
Example: Folder
class approach
@JsonbTransient
private List<Password> items = new ArrayList<>();
It's up to you now which class will not show the reference to another class. Also, see this
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.
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
}
}
Solving circular reference
@Transient
private B messageOwner;
This does for the JSON serialization what the transient
type modifier does for the normal serialization. If the JSON library supports it.
Of course the messageOwner
will be null after deserialisation.
How to Serialize circular referenced object using JSON-B (Java API for JSON Binding)
Finally I give up using JSON-B and instead use Jackson, use the annotation @JsonIdentityInfo here is my solution for information:
import java.util.ArrayList;
import java.util.List;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonTest {
private static Person person = null;
private static List<Property> propertyList = new ArrayList<>();
public static void main(String[] args) throws Exception {
person = new Person(1, "Jhon");
propertyList.add(new Property(1, person, "Palace"));
propertyList.add(new Property(2, person, "Apartment"));
person.setPropertyList(propertyList);
jacksonTest();
//jsonbTest();
}
private static void jacksonTest()
throws Exception
{
String result = new ObjectMapper().writeValueAsString(person);
System.out.println("result: " + result);
}
private static void jsonbTest()
throws Exception
{
Jsonb jsonb = JsonbBuilder.create();
/**
* stackoverflow here
*/
System.out.println("jsonPerson with property: " + jsonb.toJson(person));
}
public static class Property extends BaseEntity {
private Person person;
private String propertyName;
public Property(int id, Person person, String propertyName) {
super();
setId(id);
this.person = person;
this.propertyName = propertyName;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public String getPropertyName() {
return propertyName;
}
public void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
}
public static class Person extends BaseEntity {
public Person() {
super();
}
public Person(int id, String name) {
super();
setId(id);
this.name = name;
}
private String name;
private List<Property> propertyList = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Property> getPropertyList() {
return propertyList;
}
public void setPropertyList(List<Property> propertyList) {
this.propertyList = propertyList;
}
}
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public static abstract class BaseEntity {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
}
Jackson output:
result: {"id":1,"name":"Jhon","propertyList":[{"id":1,"person":1,"propertyName":"Palace"},{"id":2,"person":1,"propertyName":"Apartment"}]}
Related Topics
How to Hide a Jframe in System Tray of Taskbar
How to Call Super Constructor in Lombok
Arraylist Contains Case Sensitivity
Maven. Lambda Expressions Are Not Supported in -Source 1.5
How to Open a Txt File and Read Numbers in Java
In Java, How to Define an Integer Constant in Binary Format
Parsing JSON Array Within JSON Object
Displaying Fancy Equations with Java
Java Sorting Based on Enum Constants
Change Database Schema Used by Spring Boot
How Do Java Method Annotations Work in Conjunction with Method Overriding
Does Java Read Integers in Little Endian or Big Endian
When and How Are Classes Garbage Collected in Java
JSON Gson.Fromjson Java Objects
Running Java Gives "Error: Could Not Open 'C:\Program Files\Java\Jre6\Lib\Amd64\Jvm.Cfg'"