web-dev-qa-db-ja.com

Jsonおよび循環参照の例外

別のオブジェクトへの循環参照を持つオブジェクトがあります。これらのオブジェクト間の関係を考えると、これは適切な設計です。

説明する

Machine => Customer => Machine

予想通り、Jsonを使用してマシンまたはカスタマーオブジェクトをシリアル化しようとすると、問題が発生します。 MachineオブジェクトとCustomerオブジェクトの間の関係を壊したくないので、この問題をどのように解決すればよいかわかりません。この問題を解決するためのオプションは何ですか?

編集

現在、私は Controller基本クラスによって提供されるJSONメソッド を使用しています。だから私がしているシリアライゼーションは次のように基本的です:

Json(machineForm);
39
ahsteele

更新:

NonSerializedAttributeは明らかにそれを無視するため、JavaScriptSerializerを使用しないでください。

代わりに、System.Web.Script.SerializationScriptIgnoreAttributeを使用してください。

public class Machine
{
    public string Customer { get; set; }

    // Other members
    // ...
}

public class Customer
{
    [ScriptIgnore]
    public Machine Machine { get; set; }    // Parent reference?

    // Other members
    // ...
}

このように、MachineJsonメソッドにトスすると、それはMachineからCustomerへの関係をトラバースしますが、元に戻ろうとしませんCustomerからMachineへ。

関係はコードが満足するようにコードで実行するためにまだ存在しますが、JavaScriptSerializerJsonメソッドで使用)はそれを無視します。

57
Aaronaught

「json.encode循環参照」に対するGoogleからの(現在)3番目の結果であり、上記の回答に(完全に)同意しませんが、ScriptIgnoreAttributeを使用すると、コード内のどこかで、JSONの関係を逆方向にたどることはありません。ユースケースが1つしかないため、モデルをロックダウンするとは思いません。

このシンプルなソリューションを使用することに刺激を受けました。

MVCのビューで作業しているので、モデルがあり、モデルをコントローラー内のViewData.Modelに単純に割り当て、ビュー内でLINQクエリを使用してデータを適切にフラット化し、問題のあるものを削除しますこのようにしたい特定のJSONの循環参照:

var jsonMachines = from m in machineForm
                   select new { m.X, m.Y, // other Machine properties you desire
                                Customer = new { m.Customer.Id, m.Customer.Name, // other Customer properties you desire
                              }};
return Json(jsonMachines);

または、マシン->顧客関係が1 .. *-> *の場合は、次を試してください。

var jsonMachines = from m in machineForm
                   select new { m.X, m.Y, // other machine properties you desire
                                Customers = new List<Customer>(
                                               (from c in m.Customers
                                                select new Customer()
                                                {
                                                   Id = c.Id,
                                                   Name = c.Name,
                                                   // Other Customer properties you desire
                                                }).Cast<Customer>())
                               };
return Json(jsonMachines);
33
eudaimos

Txlの回答に基づいて、遅延読み込みとプロキシ作成を無効にする必要があり、通常の方法を使用してデータを取得できます。

例:

//Retrieve Items with Json:
public JsonResult Search(string id = "")
{
    db.Configuration.LazyLoadingEnabled = false;
    db.Configuration.ProxyCreationEnabled = false;

    var res = db.Table.Where(a => a.Name.Contains(id)).Take(8);

    return Json(res, JsonRequestBehavior.AllowGet);
}
10
Thomas

同じ問題がある場合に使用します。 L2EオブジェクトをIDictionaryに「フラット化」する単純な拡張メソッドを作成しました。 IDictionaryはJavaScriptSerializerによって正しくシリアル化されます。結果のJsonは、オブジェクトを直接シリアル化するのと同じです。

シリアライゼーションのレベルを制限しているので、循環参照は避けられます。 1-> nリンクテーブル(エンティティセット)も含まれません。

    private static IDictionary<string, object> JsonFlatten(object data, int maxLevel, int currLevel) {
        var result = new Dictionary<string, object>();
        var myType = data.GetType();
        var myAssembly = myType.Assembly;
        var props = myType.GetProperties();
        foreach (var prop in props) {
            // Remove EntityKey etc.
            if (prop.Name.StartsWith("Entity")) {
                continue;
            }
            if (prop.Name.EndsWith("Reference")) {
                continue;
            }
            // Do not include lookups to linked tables
            Type typeOfProp = prop.PropertyType;
            if (typeOfProp.Name.StartsWith("EntityCollection")) {
                continue;
            }
            // If the type is from my Assembly == custom type
            // include it, but flattened
            if (typeOfProp.Assembly == myAssembly) {
                if (currLevel < maxLevel) {
                    result.Add(prop.Name, JsonFlatten(prop.GetValue(data, null), maxLevel, currLevel + 1));
                }
            } else {
                result.Add(prop.Name, prop.GetValue(data, null));
            }
        }

        return result;
    }
    public static IDictionary<string, object> JsonFlatten(this Controller controller, object data, int maxLevel = 2) {
        return JsonFlatten(data, maxLevel, 1);
    }

私のアクションメソッドは次のようになります。

    public JsonResult AsJson(int id) {
        var data = Find(id);
        var result = this.JsonFlatten(data);
        return Json(result, JsonRequestBehavior.AllowGet);
    }
4
GvS

Entity Framework version 4 には、利用可能なオプションがあります:ObjectContextOptions.LazyLoadingEnabled

Falseに設定すると、「循環参照」の問題が回避されます。ただし、含めるナビゲーションプロパティを明示的にロードする必要があります。

参照してください: http://msdn.Microsoft.com/en-us/library/bb896272.aspx

2
txl

私の知る限りでは、オブジェクト参照をシリアル化することはできませんが、コピーだけは次のような汚れたハックを使用して試すことができます。

  1. お客様は、そのマシン参照をマシンのIDとしてシリアル化する必要があります
  2. Jsonコードを逆シリアル化すると、その上で単純な関数を実行して、これらのIDを適切な参照に変換できます。
1
Swizec Teller

どちらが「ルート」オブジェクトであるかを決定する必要があります。マシンがルートであるとすると、顧客はマシンのサブオブジェクトです。マシンをシリアル化すると、JSONのサブオブジェクトとして顧客がシリアル化され、顧客がシリアル化されると[〜#〜] not [〜#〜]シリアル化されますマシンへの参照。コードがマシンをデシリアライズすると、マシンのカスタマーサブオブジェクトがデシリアライズされ、カスタマーからマシンへの後方参照が復元されます。

ほとんどのシリアライゼーションライブラリは、各クラスのデシリアライゼーションの実行方法を変更するための一種のフックを提供します。そのフックを使用して、マシンクラスの逆シリアル化を変更し、マシンの顧客の後方参照を復元する必要があります。そのフックが正確に何であるかは、使用しているJSONライブラリによって異なります。

0
Nat

私がやったことは少し過激ですが、プロパティが必要ないため、循環参照の原因となる厄介なエラーが発生するため、シリアル化する前にnullに設定しました。

SessionTickets result = GetTicketsSession();
foreach(var r in result.Tickets)
{
    r.TicketTypes = null; //those two were creating the problem
    r.SelectedTicketType = null;
}
return Json(result);

プロパティが本当に必要な場合は、循環参照を保持しないが、後で元の値を復元するために使用できる重要な要素のIDを保持するビューモデルを作成できます。

0
DiSaSteR

今週も同じ問題があり、List<MyType>を要求するインターフェイスを実装する必要があったため、匿名型を使用できませんでした。ナビゲート可能なすべての関係を示す図を作成した後、MyTypeMyObjectと双方向の関係を持っていることがわかりました。これは、両方がお互いに保存しているためです。

MyObjectは実際にMyTypeを知る必要がないと判断し、それによって単方向の関係にした後、この問題は解決されました。

0
Br2