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


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");

    ChildEntity ce1 = new ChildEntity();
    ce1.setId("1");
    ce1.setDescription("first child");
    ce1.setParent(pe);

    ChildEntity ce2 = new ChildEntity();
    ce2.setId("2");
    ce2.setDescription("second child");
    ce2.setParent(pe);

    pe.getChildren().add(ce1);
    pe.getChildren().add(ce2);

    ParentEntity pe2 = new ParentEntity();
    pe2.setId("2");
    pe2.setDescription("second parent");
    pe2.setParentEntity(pe);
    pe.getParentEntities().add(pe2);

    // serialization
    ObjectMapper mapper = new ObjectMapper();
    File f = new File("parent_entity.json");
    // write to file
        mapper.writeValue(f, pe);
    // write to string
    String s = mapper.writeValueAsString(pe);
    // deserialization
    // read from file
    ParentEntity pe3 = mapper.readValue(f,ParentEntity.class);
    // read from string
    ParentEntity pe4 = mapper.readValue(s, ParentEntity.class);         
}

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.


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;

    @org.codehaus.jackson.annotate.JsonIgnore
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author", nullable = true)
    private User user;
    //...other attributes, getters and setters
}

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值