POJOをJSONにシリアル化するシリアライザーを書いていますが、循環参照の問題にこだわっています。休止状態の双方向の1対多の関係では、親が子を参照し、子の参照が親に戻り、ここでシリアライザーが停止します。 (以下のサンプルコードを参照)
このサイクルを断ち切るには?オブジェクトの所有者ツリーを取得して、オブジェクト自体が所有者階層のどこかに存在するかどうかを確認できますか?参照が循環するかどうかを確認する他の方法はありますか?またはこの問題を解決する他のアイデアはありますか?
双方向の関係をJSONで表すこともできますか?一部のデータ形式は、一部のタイプのデータモデリングに適していません。
オブジェクトグラフの走査を処理するときにサイクルを処理する1つの方法は、これまでに見たオブジェクトを追跡し(ID比較を使用)、無限サイクルを走査しないようにすることです。
Google JSON を使用して、この種の問題を処理するには、機能を使用します
次のようにAクラスとBクラスの双方向の関係があるとします
public class A implements Serializable {
private B b;
}
そしてB
public class B implements Serializable {
private A a;
}
GsonBuilderを使用して、次のようにカスタムGsonオブジェクトを取得します(NoticesetExclusionStrategiesメソッド)
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 classを見てください
Jackson 1.6(2010年9月リリース)には、このような親/子リンケージを処理するための特定の注釈ベースのサポートがあります。 http://wiki.fasterxml.com/JacksonFeatureBiDirReferences を参照してください。 ( Wayback Snapshot )
もちろん、ほとんどのJSON処理パッケージ(jackson、gson、およびflex-jsonを少なくともサポートする)を使用して、親リンクのシリアル化を既に除外できますが、本当のトリックは、逆シリアル化(親リンクを再作成)することですシリアル化側を処理するだけです。今のところは除外だけでいいかもしれませんが。
編集(2012年4月): Jackson 2. はtrueをサポートするようになりました ID参照 ( Wayback Snapshot )ので、この方法でも解決できます。
この問題に対処するために、次のアプローチを取りました(アプリケーション全体でプロセスを標準化し、コードを明確で再利用可能にします)。
コードは次のとおりです。
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)
最初のケースでは、nullがコンストラクターに提供され、除外する別のクラスを指定できます-両方のオプションが以下に追加されます
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);
Jackonを使用してシリアル化する場合は、双方向のマッピングに@ JsonBackReferenceを適用するだけで、循環参照の問題が解決されます。
注:@JsonBackReferenceは、無限再帰(StackOverflowError)を解決するために使用されます
アーサーに似たソリューションを使用しましたが、代わりにsetExclusionStrategies
を使用しました
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
使用された@Expose
jsonで必要なフィールドのgson注釈、他のフィールドは除外されます。
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
プロパティを変更して実際のオブジェクトを指すようにすることで、循環参照で実際のオブジェクトを簡単に再作成できます例。
これが私の場合に最終的に解決した方法です。これは、少なくとも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;
}
Jacksonは、循環参照を防ぐためにJsonIdentityInfo
注釈を提供します。チュートリアルを確認できます here .
このエラーは、2つのオブジェクトがある場合に発生する可能性があります。
class object1{
private object2 o2;
}
class object2{
private object1 o1;
}
シリアル化にGSonを使用すると、このエラーが発生しました:
Java.lang.IllegalStateException: circular reference error
Offending field: o1
これを解決するには、Wordのtransientキーを追加するだけです:
class object1{
private object2 o2;
}
class object2{
transient private object1 o1;
}
あなたがここで見ることができるように: なぜJava一時的なフィールドがあるのですか?
Javaのtransientキーワードは、フィールドをシリアル化しないことを示すために使用されます。