Jackson-deserialization fails on circular dependencies(JackSon无限递归问题)


                    <div class="row">
                        <div class="col">

Ok, so I'm trying to test some stuffs with jackson json converter. I'm trying to simulate a graph behaviour, so these are my POJO entities

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class ParentEntity implements java.io.Serializable
{   
    private String id;
    private String description;
    private ParentEntity parentEntity;
    private List<ParentEntity> parentEntities = new ArrayList<ParentEntity>(0);
    private List<ChildEntity> children = new ArrayList<ChildEntity>(0);
    // ... getters and setters
}

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class ChildEntity implements java.io.Serializable
{
private String id;
private String description;
private ParentEntity parent;
// ... getters and setters
}

The tags are required in order to avoid exception on serialization. When I try to serialize an object (both on a file or on a simple string) all works fine. However, when I try to deserialize the object, it throws an exception. This is the simple test method (try/catch omitted for simplicity)

{
    // create some entities, assigning them some values
    ParentEntity pe = new ParentEntity();
    pe.setId("1");
    pe.setDescription("first parent");
</span><span class="typ">ChildEntity</span><span class="pln"> ce1 </span><span class="pun">=</span><span class="pln"> </span><span class="kwd"><span class="hljs-keyword">new</span></span><span class="pln"> </span><span class="typ">ChildEntity</span><span class="pun">();</span><span class="pln">
ce1</span><span class="pun">.</span><span class="pln">setId</span><span class="pun">(</span><span class="str"><span class="hljs-string">"1"</span></span><span class="pun">);</span><span class="pln">
ce1</span><span class="pun">.</span><span class="pln">setDescription</span><span class="pun">(</span><span class="str"><span class="hljs-string">"first child"</span></span><span class="pun">);</span><span class="pln">
ce1</span><span class="pun">.</span><span class="pln">setParent</span><span class="pun">(</span><span class="pln">pe</span><span class="pun">);</span><span class="pln">

</span><span class="typ">ChildEntity</span><span class="pln"> ce2 </span><span class="pun">=</span><span class="pln"> </span><span class="kwd"><span class="hljs-keyword">new</span></span><span class="pln"> </span><span class="typ">ChildEntity</span><span class="pun">();</span><span class="pln">
ce2</span><span class="pun">.</span><span class="pln">setId</span><span class="pun">(</span><span class="str"><span class="hljs-string">"2"</span></span><span class="pun">);</span><span class="pln">
ce2</span><span class="pun">.</span><span class="pln">setDescription</span><span class="pun">(</span><span class="str"><span class="hljs-string">"second child"</span></span><span class="pun">);</span><span class="pln">
ce2</span><span class="pun">.</span><span class="pln">setParent</span><span class="pun">(</span><span class="pln">pe</span><span class="pun">);</span><span class="pln">

pe</span><span class="pun">.</span><span class="pln">getChildren</span><span class="pun">().</span><span class="pln">add</span><span class="pun">(</span><span class="pln">ce1</span><span class="pun">);</span><span class="pln">
pe</span><span class="pun">.</span><span class="pln">getChildren</span><span class="pun">().</span><span class="pln">add</span><span class="pun">(</span><span class="pln">ce2</span><span class="pun">);</span><span class="pln">

</span><span class="typ">ParentEntity</span><span class="pln"> pe2 </span><span class="pun">=</span><span class="pln"> </span><span class="kwd"><span class="hljs-keyword">new</span></span><span class="pln"> </span><span class="typ">ParentEntity</span><span class="pun">();</span><span class="pln">
pe2</span><span class="pun">.</span><span class="pln">setId</span><span class="pun">(</span><span class="str"><span class="hljs-string">"2"</span></span><span class="pun">);</span><span class="pln">
pe2</span><span class="pun">.</span><span class="pln">setDescription</span><span class="pun">(</span><span class="str"><span class="hljs-string">"second parent"</span></span><span class="pun">);</span><span class="pln">
pe2</span><span class="pun">.</span><span class="pln">setParentEntity</span><span class="pun">(</span><span class="pln">pe</span><span class="pun">);</span><span class="pln">
pe</span><span class="pun">.</span><span class="pln">getParentEntities</span><span class="pun">().</span><span class="pln">add</span><span class="pun">(</span><span class="pln">pe2</span><span class="pun">);</span><span class="pln">

</span><span class="com"><span class="hljs-comment">// serialization</span></span><span class="pln">
</span><span class="typ">ObjectMapper</span><span class="pln"> mapper </span><span class="pun">=</span><span class="pln"> </span><span class="kwd"><span class="hljs-keyword">new</span></span><span class="pln"> </span><span class="typ">ObjectMapper</span><span class="pun">();</span><span class="pln">
</span><span class="typ">File</span><span class="pln"> f </span><span class="pun">=</span><span class="pln"> </span><span class="kwd"><span class="hljs-keyword">new</span></span><span class="pln"> </span><span class="typ">File</span><span class="pun">(</span><span class="str"><span class="hljs-string">"parent_entity.json"</span></span><span class="pun">);</span><span class="pln">
</span><span class="com"><span class="hljs-comment">// write to file</span></span><span class="pln">
    mapper</span><span class="pun">.</span><span class="pln">writeValue</span><span class="pun">(</span><span class="pln">f</span><span class="pun">,</span><span class="pln"> pe</span><span class="pun">);</span><span class="pln">
</span><span class="com"><span class="hljs-comment">// write to string</span></span><span class="pln">
</span><span class="typ">String</span><span class="pln"> s </span><span class="pun">=</span><span class="pln"> mapper</span><span class="pun">.</span><span class="pln">writeValueAsString</span><span class="pun">(</span><span class="pln">pe</span><span class="pun">);</span><span class="pln">
</span><span class="com"><span class="hljs-comment">// deserialization</span></span><span class="pln">
</span><span class="com"><span class="hljs-comment">// read from file</span></span><span class="pln">
</span><span class="typ">ParentEntity</span><span class="pln"> pe3 </span><span class="pun">=</span><span class="pln"> mapper</span><span class="pun">.</span><span class="pln">readValue</span><span class="pun">(</span><span class="pln">f</span><span class="pun">,</span><span class="typ">ParentEntity</span><span class="pun">.</span><span class="kwd">class</span><span class="pun">);</span><span class="pln">
</span><span class="com"><span class="hljs-comment">// read from string</span></span><span class="pln">
</span><span class="typ">ParentEntity</span><span class="pln"> pe4 </span><span class="pun">=</span><span class="pln"> mapper</span><span class="pun">.</span><span class="pln">readValue</span><span class="pun">(</span><span class="pln">s</span><span class="pun">,</span><span class="pln"> </span><span class="typ">ParentEntity</span><span class="pun">.</span><span class="kwd">class</span><span class="pun">);</span><span class="pln">         

}

and this is the exception thrown (of course, repeated twice)

com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id (java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey@3372bb3f] (through reference chain: ParentEntity["children"]->java.util.ArrayList[0]->ChildEntity["id"])
...stacktrace...
Caused by: java.lang.IllegalStateException: Already had POJO for id (java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey@3372bb3f]
...stacktrace...

So, what is the cause of the problem? How can I fix it? Do I need some other annotation?


Answers

I have done this using org.codehaus.jackson.annotate.JsonManagedReference and org.codehaus.jackson.annotate.JsonBackReference in this way...

look at how i used @JsonManagedReference

 @Id
 @TableGenerator(name="CatId", table="catTable",pkColumnName="catKey",pkColumnValue="catValue", allocationSize=1)
 @GeneratedValue(strategy=GenerationType.TABLE, generator="CatId")
 @Column(name = "CategId", unique = true, nullable = false)
 private long categoryId;
 private String productCategory;
 @JsonManagedReference("product-category")
 @OneToMany(targetEntity=ProductDatabase.class,mappedBy="category", cascade=CascadeType.ALL, fetch=FetchType.EAGER)
 private List<ProductDatabase> catProducts;

and then at the other end i used @JsonBackReference as shown below.

@Id@GeneratedValue
private int productId;
private String description;
private int productPrice;
private String productName;
private String ProductImageURL;
@JsonBackReference("product-category")
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "CategId")
private Category category;

just apply these annotations and check if it works for you.

                    <div class="row">
                        <div class="col">
                            <div id="answers">
                                                                        <hr>
                                    <article id="answer1">
                                        <div class="float-left">

Actually, it seems that the problem was with the "id" property. Because the name of the property is equal for the two different entities, there were some problems during deserialization. Don't know if it makes sense at all, but I solved the problem changing the JsonIdentityInfo tag of ParentEntity to

@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = ParentEntity.class))

Of course, I also changed the scope of ChildEntity with scope=ChildEntity.class as suggested here

I'm open to new answer and suggestions by the way.


Hibernate and JSON - is there a definitive solution to circular dependencies?


Jackson

As said, I was able to solve the problem using

@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id", scope=MyEntity.class)` 

for each entity as suggested here. The scope attribute was necessary to make sure that the name "id" is unique within the scope. Actually, without the scope attribute, as you can see here, it throws an exception saying

com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey@3372bb3f] (through reference chain: ParentEntity["children"]->java.util.ArrayList[0]->ChildEntity["id"])
...stacktrace...
Caused by: java.lang.IllegalStateException: Already had POJO for id (java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey@3372bb3f]
...stacktrace...

Gson

I still haven't found a clean way to serialize circular dependencies.


When dealing with circular dependencies you need to build a parent-children JSON hierarchy, because the marshalling must be cascaded from root to the inner-most child.

For bi-directional associations, when the Parent has a one-to-many children collection and the child has a many-to-one reference to Parent, you need to annotate the many-to-one side with @JsonIgnore:

@Entity
@Table(name = "thread")
public class Thread extends RecognizedServerEntities implements java.io.Serializable
{
    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Integer id;
</span><span class="lit"><span class="hljs-meta">@org</span></span><span class="pun">.</span><span class="pln">codehaus</span><span class="pun">.</span><span class="pln">jackson</span><span class="pun">.</span><span class="pln">annotate</span><span class="pun">.</span><span class="typ">JsonIgnore</span><span class="pln">
</span><span class="lit"><span class="hljs-meta">@ManyToOne</span></span><span class="pun">(</span><span class="pln">fetch </span><span class="pun">=</span><span class="pln"> </span><span class="typ">FetchType</span><span class="pun">.</span><span class="pln">LAZY</span><span class="pun">)</span><span class="pln">
</span><span class="lit"><span class="hljs-meta">@JoinColumn</span></span><span class="pun">(</span><span class="pln">name </span><span class="pun">=</span><span class="pln"> </span><span class="str"><span class="hljs-string">"author"</span></span><span class="pun">,</span><span class="pln"> nullable </span><span class="pun">=</span><span class="pln"> </span><span class="kwd"><span class="hljs-keyword">true</span></span><span class="pun">)</span><span class="pln">
</span><span class="kwd"><span class="hljs-keyword">private</span></span><span class="pln"> </span><span class="typ">User</span><span class="pln"> user</span><span class="pun">;</span><span class="pln">
</span><span class="com"><span class="hljs-comment">//...other attributes, getters and setters</span></span><span class="pln">

}

This way you will no longer have a Json serializing-time circular dependency.


JsonMappingException: Already had POJO for id

You should use scope property when annotating the ids. Then the de-serializer would make sure the id is unique within the scope.

from Annotation Type JsonIdentityInfo:

Scope is used to define applicability of an Object Id: all ids must be unique within their scope; where scope is defined as combination of this value and generator type.

e.g. @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class,property="@id", scope = Account.class)


Handling recursive reference with collections (many to many) on Json Resteasy

Try adding this annotations to the classes:

@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = UnidadAsistencialDTO.class))

And

@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = ServicioDTO.class))

And also take a look at this for further reference.

EDIT

If this doesn't work (as in your case) try this instead:

@JsonIgnore

Docs of this are here.

UPDATE

If you need to have a parent with their childs list AND the child with a reference to the parent, I suggest you go either way of:

  1. Delete the reference of the parent in the childs when calling the parent, and delete the reference of the childs if calling the childs (just in-memory and right-before transforming the DTO's to json). You'll end up with something like this:

    { "name":"theParent", "childs":[ { "name":"child1", "parent":null },{ "name":"child2", "parent":null } ] }

    Nothe the null in the reference to the parent from the childs. Also, as a tip, make sure you don't delete the parent reference INSIDE a transaction context.

  2. If having a null reference inside your child (or parent, depending what you're quering) isn't correct, you can make a new set of DTOs that map to the structure you're looking for. If you go for this, take a look at this for the right way to do it.


Gson 1.6 now includes a low-level streaming API and a new parser which is actually faster than Jackson.

原文地址:https://code.i-harness.com/en/q/19762c2
【推广】 免费学中医,健康全家人
原文地址:https://www.cnblogs.com/jpfss/p/11065054.html