2010-07-27 53 views
67

我正在编写一个序列化程序来将POJO序列化为JSON,但卡住了循环引用问题。在hibernate双向一对多关系中,父级引用子对象和子对象引用回父对象,在这里我的序列化程序会死亡。 (请参阅下面的示例代码)
如何打破此循环?我们能否获得对象的所有者树来查看对象本身是否存在于它自己的所有者层次结构中的某个位置?任何其他方式来查找参考是否将是循环?或任何其他想法来解决这个问题?如何解决由休眠双向映射引起的json串行器中的循环引用?

+0

您的意思是在一些代码粘贴无限递归(的StackOverflowError)让我们帮您解决问题? – Russell 2010-07-27 03:14:38

+2

eugene基于注解的解决方案没问题,但在这种情况下不需要额外的注释和ExclusionStrategy实施。只需使用Java'[transient](http://stackoverflow.com/questions/910374/why-does-java-have-transient-variables)'关键字即可。它适用于标准Java对象序列化,但也适用于[Gson尊重它](https://sites.google.com/site/gson/gson-user-guide#TOC-Java-Modifier-Exclusion)。 – MeTTeO 2012-05-20 10:04:37

回答

9

双向关系甚至可以用JSON表示吗?某些数据格式不适合某些类型的数据建模。

在处理遍历对象图时处理周期的一种方法是跟踪您迄今为止看到的哪些对象(使用身份比较),以防止自己遍历无限循环。

+0

我做了同样的工作,但不知道它是否适用于所有的映射场景。至少现在我已经设定好了,并会继续思考更优雅的想法 – WSK 2010-07-27 14:22:59

+0

当然,他们可以 - 这里没有原生的数据类型或结构。但是任何东西都可以用XML,JSON或大多数其他数据格式表示。 – StaxMan 2010-07-29 06:07:28

+4

我很好奇 - 你会如何代表JSON中的循环引用? – 2010-07-29 12:05:53

46

我靠Google JSON要使用该功能处理这类问题的

Excluding Fields From Serialization and Deserialization

假设A和B类之间的双向关系如下

public class A implements Serializable { 

    private B b; 

} 

和B

public class B implements Serializable { 

    private A a; 

} 

现在使用GsonBuilder获得自定义GSON对象,如下所示(请注意setExclusionStrategies法)

Gson gson = new GsonBuilder() 
    .setExclusionStrategies(new ExclusionStrategy() { 

     public boolean shouldSkipClass(Class<?> clazz) { 
      return (clazz == B.class); 
     } 

     /** 
      * Custom field exclusion goes here 
      */ 
     public boolean shouldSkipField(FieldAttributes f) { 
      return false; 
     } 

    }) 
    /** 
     * Use serializeNulls method if you want To serialize null values 
     * By default, Gson does not serialize null values 
     */ 
    .serializeNulls() 
    .create(); 

现在我们的循环引用

A a = new A(); 
B b = new B(); 

a.setB(b); 
b.setA(a); 

String json = gson.toJson(a); 
System.out.println(json); 

GsonBuilder类看一看

+0

谢谢亚瑟你的建议,但真正的问题是什么是建立所谓的通用“shouldSkipClass”方法的最佳方法。现在我开始研究马特的想法并解决了我的问题,但仍然怀疑,将来这种解决方案可能会破坏特定的场景。 – WSK 2010-07-27 14:26:17

+5

通过删除它们来“解决”循环引用。没有办法从生成的JSON重建原始数据结构。 – 2015-02-17 20:45:39

33

Jackson 1.6(2010年9月发布)具有针对手的特定注解支持ling这样的亲子关系,见http://wiki.fasterxml.com/JacksonFeatureBiDirReferences。 (Wayback Snapshot

你当然已经可以排除已经使用大多数JSON处理包(jackson,gson和flex-json至少支持它)的父链接的序列化,但真正的技巧是如何反序列化它 - 创建父链接),而不仅仅是处理序列化的一面。虽然现在只是排除声音可能适合你。

编辑(2012年4月):Jackson 2.0现在支持真identity referencesWayback Snapshot),所以你可以这样解决它。

+3

链接不起作用。 – LOLKFC 2014-05-18 17:13:56

+0

如何得到这个工作,当你的方向并不总是相同的..我试着把两个注释都放在字段上,但它没有工作: A类{@@JsonBackReference(“abc”) @JsonManagedReference “xyz”) private B b; } B类{@JsonManagedReference(“abc”) @JsonBackReference(“xyz”) private A a; } – azi 2014-12-15 13:32:11

+0

按照上面所述,对象ID(@JsonIdentityInfo)是使常规引用起作用的方式。托管/后退引用确实需要某些指导,所以它们不适用于您的案例。 – StaxMan 2014-12-15 17:27:06

-11

例如,ProductBean有serialBean。该映射将是双向关系。如果我们现在尝试使用gson.toJson(),它将以循环引用结束。为了避免这个问题,您可以按照下列步骤操作:

  1. 检索数据源中的结果。
  2. 迭代列表,并确保serialBean不为空,然后
  3. 设置productBean.serialBean.productBean = null;
  4. 然后尝试使用gson.toJson();

这应该解决的问题

12

在解决这一问题,我采取了以下方法(标准化我的应用程序的过程,使代码清晰且可重复使用):

  1. 创建一个注解类要在领域中你想排除
  2. 定义一个实现谷歌的ExclusionStrategy接口的类
  3. 创建一个简单的方法来生成使用GsonBuilder(类似于亚瑟的说明)GSON对象
  4. 注释字段被排除在外,因为需要
  5. 应用序列化规则,以你的com.google.gson.Gson对象
  6. 序列化你的对象

下面的代码:

1)

import java.lang.annotation.ElementType; 
import java.lang.annotation.Retention; 
import java.lang.annotation.RetentionPolicy; 
import java.lang.annotation.Target; 

@Retention(RetentionPolicy.RUNTIME) 
@Target({ElementType.FIELD, ElementType.METHOD}) 
public @interface GsonExclude { 

} 

2)

import com.google.gson.ExclusionStrategy; 
import com.google.gson.FieldAttributes; 

public class GsonExclusionStrategy implements ExclusionStrategy{ 

    private final Class<?> typeToExclude; 

    public GsonExclusionStrategy(Class<?> clazz){ 
     this.typeToExclude = clazz; 
    } 

    @Override 
    public boolean shouldSkipClass(Class<?> clazz) { 
     return (this.typeToExclude != null && this.typeToExclude == clazz) 
        || clazz.getAnnotation(GsonExclude.class) != null; 
    } 

    @Override 
    public boolean shouldSkipField(FieldAttributes f) { 
     return f.getAnnotation(GsonExclude.class) != null; 
    } 

} 

3)

static Gson createGsonFromBuilder(ExclusionStrategy exs){ 
    GsonBuilder gsonbuilder = new GsonBuilder(); 
    gsonbuilder.setExclusionStrategies(exs); 
    return gsonbuilder.serializeNulls().create(); 
} 

4)

public class MyObjectToBeSerialized implements Serializable{ 

    private static final long serialVersionID = 123L; 

    Integer serializeThis; 
    String serializeThisToo; 
    Date optionalSerialize; 

    @GsonExclude 
    @ManyToOne(fetch=FetchType.LAZY, optional=false) 
    @JoinColumn(name="refobj_id", insertable=false, updatable=false, nullable=false) 
    private MyObjectThatGetsCircular dontSerializeMe; 

    ...GETTERS AND SETTERS... 
} 

5)

在第一种情况,空被提供给构造,可以指定另一个类要排除 - 两个选项都加入以下

Gson gsonObj = createGsonFromBuilder(new GsonExclusionStrategy(null)); 
Gson _gsonObj = createGsonFromBuilder(new GsonExclusionStrategy(Date.class)); 

6)

MyObjectToBeSerialized _myobject = someMethodThatGetsMyObject(); 
String jsonRepresentation = gsonObj.toJson(_myobject); 

或者排除Date对象

String jsonRepresentation = _gsonObj.toJson(_myobject); 
+0

这可以防止子对象被处理,我可以包括子JSON,然后停止循环 – ir2pid 2014-06-11 12:51:30

1

如果您使用的是Javascript ,使用JSON.stringify()方法的replacer参数有一个非常简单的解决方案,您可以在其中传递函数以修改默认的序列化行为。

以下是您可以如何使用它的方法。考虑下面的例子,在循环图中有4个节点。

// node constructor 
function Node(key, value) { 
    this.name = key; 
    this.value = value; 
    this.next = null; 
} 

//create some nodes 
var n1 = new Node("A", 1); 
var n2 = new Node("B", 2); 
var n3 = new Node("C", 3); 
var n4 = new Node("D", 4); 

// setup some cyclic references 
n1.next = n2; 
n2.next = n3; 
n3.next = n4; 
n4.next = n1; 

function normalStringify(jsonObject) { 
    // this will generate an error when trying to serialize 
    // an object with cyclic references 
    console.log(JSON.stringify(jsonObject)); 
} 

function cyclicStringify(jsonObject) { 
    // this will successfully serialize objects with cyclic 
    // references by supplying @name for an object already 
    // serialized instead of passing the actual object again, 
    // thus breaking the vicious circle :) 
    var alreadyVisited = []; 
    var serializedData = JSON.stringify(jsonObject, function(key, value) { 
     if (typeof value == "object") { 
      if (alreadyVisited.indexOf(value.name) >= 0) { 
       // do something other that putting the reference, like 
       // putting some name that you can use to build the 
       // reference again later, for eg. 
       return "@" + value.name; 
      } 
      alreadyVisited.push(value.name); 
     } 
     return value; 
    }); 
    console.log(serializedData); 
} 

后来,你可以很容易地通过解析序列化的数据和修改next财产,如果它的使用就像这个例子用@命名引用指向实际对象重新创建具有循环引用实际的对象。

2

采用类似于亚瑟的一个解决方案,但不是setExclusionStrategies我用

Gson gson = new GsonBuilder() 
       .excludeFieldsWithoutExposeAnnotation() 
       .create(); 

和使用,我需要在JSON领域@Expose GSON注释等领域被排除在外。

+0

谢谢你的家伙。这对我有效。经过长时间的研究,我终于找到了摆脱这个错误的方法。 – kepy97 2018-03-05 15:33:39

-3

答案编号8是更好的,我认为如果你知道什么领域是抛出一个错误,你只能将fild设置为null并解决。

List<RequestMessage> requestMessages = lazyLoadPaginated(first, pageSize, sortField, sortOrder, filters, joinWith); 
    for (RequestMessage requestMessage : requestMessages) { 
     Hibernate.initialize(requestMessage.getService()); 
     Hibernate.initialize(requestMessage.getService().getGroupService()); 
     Hibernate.initialize(requestMessage.getRequestMessageProfessionals()); 
     for (RequestMessageProfessional rmp : requestMessage.getRequestMessageProfessionals()) { 
      Hibernate.initialize(rmp.getProfessional()); 
      rmp.setRequestMessage(null); // ** 
     } 
    } 

为了使代码可读性大的注释将从注释// **移动到下面。

java.lang.StackOverflowError [请求处理失败;嵌套异常是org.springframework.http.converter.HttpMessageNotWritableException:无法写入JSON:无限递归(StackOverflowError)(通过引用链:com.service.pegazo.bo.RequestMessageProfessional [“requestMessage”] - > com.service.pegazo。 bo.RequestMessage [ “requestMessageProfessionals”]

+1

没有“答案编号8”,你最好给出参考答案的作者姓名。你在这里发布的文字是不可读的,请看看答案是如何发布的,并尝试将它们整齐排列。最后我不明白这是如何回答原来的问题。请添加更多的细节来解释答案。 – AdrianHHH 2015-11-20 12:16:48

0

,当你有两个对象这个错误可以appened:

class object1{ 
    private object2 o2; 
} 

class object2{ 
    private object1 o1; 
} 

随着使用GSON系列化,我有这个错误:

java.lang.IllegalStateException: circular reference error 

Offending field: o1 

要解决这一点,只需添加关键字瞬间:

class object1{ 
    private object2 o2; 
} 

class object2{ 
    transient private object1 o1; 
} 

正如你可以在这里看到:Why does Java have transient fields?

Java中的瞬态关键字用来表明某个字段不应被序列化。

1

这就是我终于如何解决它在我的情况。这至少与Gson & Jackson有效。

private static final Gson gson = buildGson(); 

private static Gson buildGson() { 
    return new GsonBuilder().addSerializationExclusionStrategy(getExclusionStrategy()).create(); 
} 

private static ExclusionStrategy getExclusionStrategy() { 
    ExclusionStrategy exlStrategy = new ExclusionStrategy() { 
     @Override 
     public boolean shouldSkipField(FieldAttributes fas) { 
      return (null != fas.getAnnotation(ManyToOne.class)); 
     } 
     @Override 
     public boolean shouldSkipClass(Class<?> classO) { 
      return (null != classO.getAnnotation(ManyToOne.class)); 
     } 
    }; 
    return exlStrategy; 
} 
3

如果使用同时杰克逊序列化,只适用@JsonBackReference到您的双向directinal映射 它将解决循环引用问题。

注:@JsonBackReference是用来解决

+0

'@ JsonIgnore'使我的'JpaRepository'无法映射属性,但'@ JsonBackReference'解决了循环引用,并仍然允许对有问题的属性进行适当的映射 – Bramastic 2017-08-15 23:54:58