web-dev-qa-db-ja.com

Newtonsoft.Jsonを使用して循環参照オブジェクトを「実際に」シリアル化するにはどうすればよいですか?

Newtonsoft.Jsonを使用してASP.NETWebAPIコントローラーからデータを正しくシリアル化するのに問題があります。

これが私がしていることですthink起こっています-私が間違っているなら私を訂正してください。特定の状況下(特にデータに循環参照がない場合)では、すべてが期待どおりに機能します。つまり、入力されたオブジェクトのリストがシリアル化されて返されます。モデルに循環参照を引き起こすデータを導入すると(以下で説明し、PreserveReferencesHandling.Objectsが設定されている場合でも)、循環参照を持つ最初のオブジェクトに至るまでのリストの要素のみが、クライアントがシリアル化する方法でシリアル化されます。 「一緒に働く」ことができます。 「までの要素」は、シリアライザーに送信する前に順序が異なる場合、データ内の任意の要素にすることができますが、少なくとも1つは、クライアントが「処理」できる方法でシリアル化されます。空のオブジェクトは、Newtonsoft参照({$ref:X})としてシリアル化されます。

たとえば、次のようなナビゲーションプロパティを備えたEFモデルがある場合:

Model

私のglobal.asaxで:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;

Entity Frameworkを使用して行っている基本的なクエリは次のとおりです(遅延読み込みがオフになっているため、ここにはプロキシクラスはありません)。

[HttpGet]
[Route("starting")]
public IEnumerable<Balance> GetStartingBalances()
{
   using (MyContext db = new MyContext())
   {
       var data = db.Balances
        .Include(x => x.Source)
        .Include(x => x.Place)
        .ToList()
       return data;
    }
}

これまでのところ、dataが入力されています。

循環参照がない場合、人生は壮大です。ただし、同じBalanceまたはSourceを持つ2つのPlaceエンティティがあるとすぐに、シリアル化により、最上位の遅いBalanceオブジェクトが回転します。 BalancesまたはSourceオブジェクトのPlaceプロパティですでにシリアル化されているため、本格的なオブジェクトではなく、Newtonsoft参照に戻ることをリストします。

[{"$id":"1","BalanceID":4,"SourceID":2,"PlaceID":2 ...Omitted for clarity...},{"$ref":"4"}]

これに伴う問題は、私たち人間が何が起こっているのかを理解していても、クライアントが{$ref:4}をどうするかを知らないことです。私の場合、これは、AngularJSを使用してこのJSONを使用した残高のリスト全体をng-repeatすることができないことを意味します。これは、それらがすべてBalanceプロパティを持つ真のBalanceオブジェクトではないためです縛る。同じ問題を抱えるユースケースは他にもたくさんあると思います。

json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objectsをオフにすることはできません。他の多くのものが壊れてしまうためです(これは、ここや他の場所にある100の他の質問で十分に文書化されています)。

Web APIコントローラーのエンティティを調べて実行する以外に、これに対するより良い回避策はありますか?

Balance.Source.Balances = null;

循環参照を壊すためにすべてのナビゲーションプロパティに?それも正しくないように思われるからです。

16
jgoeglein

はい、PreserveReferencesHandling.Objectsを使用することは、循環参照を使用してオブジェクトグラフをシリアル化するための実際の最良の方法です。これは、最もコンパクトなJSONを生成し、オブジェクトグラフの参照構造を実際に保持するためです。つまり、JSONをオブジェクトに逆シリアル化すると($idおよび$ref表記を理解するライブラリを使用して)、特定のオブジェクトへの各参照は、ではなく、そのオブジェクトの同じインスタンスを指します。同じデータを持つ複数のインスタンスを持つ。

あなたの場合、問題は、クライアント側のパーサーがJson.Netによって生成された$idおよび$ref表記を理解しないため、参照が解決されないことです。これは、JavaScriptメソッドを使用して、JSONを逆シリアル化した後にオブジェクト参照を再構築することで修正できます。例については、 ここ および ここ を参照してください。

状況によっては、ReferenceLoopHandlingIgnoreに設定する代わりに、シリアル化時にPreserveReferencesHandlingObjectsに設定することもできます。ただし、これは完全な解決策ではありません。 ReferenceLoopHandling.IgnorePreserveReferencesHandling.Objectsの使用の違いの詳細な説明については、 この質問 を参照してください。

19
Brian Rogers