web-dev-qa-db-ja.com

休止状態の双方向マッピングに起因するJSONシリアライザーの循環参照を解決する方法は?

POJOをJSONにシリアル化するシリアライザーを書いていますが、循環参照の問題にこだわっています。休止状態の双方向の1対多の関係では、親が子を参照し、子の参照が親に戻り、ここでシリアライザーが停止します。 (以下のサンプルコードを参照)
このサイクルを断ち切るには?オブジェクトの所有者ツリーを取得して、オブジェクト自体が所有者階層のどこかに存在するかどうかを確認できますか?参照が循環するかどうかを確認する他の方法はありますか?またはこの問題を解決する他のアイデアはありますか?

74
WSK

双方向の関係をJSONで表すこともできますか?一部のデータ形式は、一部のタイプのデータモデリングに適していません。

オブジェクトグラフの走査を処理するときにサイクルを処理する1つの方法は、これまでに見たオブジェクトを追跡し(ID比較を使用)、無限サイクルを走査しないようにすることです。

10
matt b

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を見てください

45
Arthur Ronald

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 )ので、この方法でも解決できます。

33
StaxMan

この問題に対処するために、次のアプローチを取りました(アプリケーション全体でプロセスを標準化し、コードを明確で再利用可能にします)。

  1. 除外するフィールドで使用する注釈クラスを作成します
  2. Googleの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)

最初のケースでは、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);
12
eugene

Jackonを使用してシリアル化する場合は、双方向のマッピングに@ JsonBackReferenceを適用するだけで、循環参照の問題が解決されます。

注:@JsonBackReferenceは、無限再帰(StackOverflowError)を解決するために使用されます

3
Praveen Shendge

アーサーに似たソリューションを使用しましたが、代わりにsetExclusionStrategiesを使用しました

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

使用された@Expose jsonで必要なフィールドのgson注釈、他のフィールドは除外されます。

2
gndp

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プロパティを変更して実際のオブジェクトを指すようにすることで、循環参照で実際のオブジェクトを簡単に再作成できます例。

1
abhishekcghosh

これが私の場合に最終的に解決した方法です。これは、少なくとも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;
} 
1
pirho

Jacksonは、循環参照を防ぐためにJsonIdentityInfo注釈を提供します。チュートリアルを確認できます here .

0
Ankur Mahajan

このエラーは、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キーワードは、フィールドをシリアル化しないことを示すために使用されます。

0
Kevin ABRIOUX