web-dev-qa-db-ja.com

Json.NetのPreserveReferencesHandlingとReferenceLoopHandlingの違いは何ですか?

私はこれがコーディングされている1つのWebAPIアプリケーションサンプルを見ています:

json.SerializerSettings.PreserveReferencesHandling 
   = Newtonsoft.Json.PreserveReferencesHandling.Objects;

そして、これがコード化された別のもの:

json.SerializerSettings.ReferenceLoopHandling 
   = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

また、それぞれが選択される理由を説明しません。私はWebAPIが初めてなので、誰かが違いを簡単に説明してくれるので、違いを理解し、なぜ他のものよりも使用する必要があるかもしれません。

57
user3568783

これらの設定は、例で最もよく説明できます。会社の従業員の階層を表現したいとしましょう。そのため、次のような単純なクラスを作成します。

class Employee
{
    public string Name { get; set; }
    public List<Employee> Subordinates { get; set; }
}

これはこれまでのところ、アンジェラ、ボブ、チャールズの3人の従業員しかいない小さな会社です。アンジェラはボス、ボブとチャールズは彼女の部下です。この関係を説明するデータを設定しましょう:

Employee angela = new Employee { Name = "Angela Anderson" };
Employee bob = new Employee { Name = "Bob Brown" };
Employee charles = new Employee { Name = "Charles Cooper" };

angela.Subordinates = new List<Employee> { bob, charles };

List<Employee> employees = new List<Employee> { angela, bob, charles };

従業員のリストをJSONにシリアル化すると...

string json = JsonConvert.SerializeObject(employees, Formatting.Indented);
Console.WriteLine(json);

...この出力が得られます:

[
  {
    "Name": "Angela Anderson",
    "Subordinates": [
      {
        "Name": "Bob Brown",
        "Subordinates": null
      },
      {
        "Name": "Charles Cooper",
        "Subordinates": null
      }
    ]
  },
  {
    "Name": "Bob Brown",
    "Subordinates": null
  },
  {
    "Name": "Charles Cooper",
    "Subordinates": null
  }
]

ここまでは順調ですね。ただし、ボブとチャールズの情報はJSONで繰り返されることに気付くでしょう。なぜなら、それらを表すオブジェクトは従業員のメインリストと部下のアンジェラのリストの両方で参照されるからです。多分それは今のところ大丈夫です。

ここで、各従業員の部下に加えて、各従業員の上司を追跡する方法も用意したいとします。 Employeeモデルを変更してSupervisorプロパティを追加します...

class Employee
{
    public string Name { get; set; }
    public Employee Supervisor { get; set; }
    public List<Employee> Subordinates { get; set; }
}

...さらにセットアップコードに2、3行追加して、CharlesとBobがAngelaに報告することを示します。

Employee angela = new Employee { Name = "Angela Anderson" };
Employee bob = new Employee { Name = "Bob Brown" };
Employee charles = new Employee { Name = "Charles Cooper" };

angela.Subordinates = new List<Employee> { bob, charles };
bob.Supervisor = angela;       // added this line
charles.Supervisor = angela;   // added this line

List<Employee> employees = new List<Employee> { angela, bob, charles };

しかし、今は少し問題があります。オブジェクトグラフには参照ループがあるため(たとえば、angelabobを参照し、bobangelaを参照します)、JsonSerializationExceptionを取得します。従業員リストをシリアル化しようとしたとき。この問題を回避する1つの方法は、次のようにReferenceLoopHandlingIgnoreに設定することです。

JsonSerializerSettings settings = new JsonSerializerSettings
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
    Formatting = Formatting.Indented
};

string json = JsonConvert.SerializeObject(employees, settings);

この設定を行うと、次のJSONが取得されます。

[
  {
    "Name": "Angela Anderson",
    "Supervisor": null,
    "Subordinates": [
      {
        "Name": "Bob Brown",
        "Subordinates": null
      },
      {
        "Name": "Charles Cooper",
        "Subordinates": null
      }
    ]
  },
  {
    "Name": "Bob Brown",
    "Supervisor": {
      "Name": "Angela Anderson",
      "Supervisor": null,
      "Subordinates": [
        {
          "Name": "Charles Cooper",
          "Subordinates": null
        }
      ]
    },
    "Subordinates": null
  },
  {
    "Name": "Charles Cooper",
    "Supervisor": {
      "Name": "Angela Anderson",
      "Supervisor": null,
      "Subordinates": [
        {
          "Name": "Bob Brown",
          "Subordinates": null
        }
      ]
    },
    "Subordinates": null
  }
]

JSONを調べると、この設定が何をするのかが明確になります。シリアライザーが既にシリアライズ処理中のオブジェクトへの参照を検出すると、そのメンバーは単にスキップされます。 (これにより、シリアライザーが無限ループに入るのを防ぎます。)JSONの上部にあるアンジェラの部下のリストでは、ボブもチャールズもスーパーバイザーを表示していません。 JSONの下部では、ボブとチャールズの両方がスーパーバイザーとしてアンジェラを示していますが、その時点で彼女の部下のリストにはボブとチャールズの両方が含まれていないことに注意してください。

このJSONを使用し、場合によってはそこから元のオブジェクト階層を再構築することも可能ですが、明らかに最適ではありません。代わりにPreserveReferencesHandling設定を使用することにより、オブジェクト参照を保持しながら、JSONで繰り返される情報を排除できます。

JsonSerializerSettings settings = new JsonSerializerSettings
{
    PreserveReferencesHandling = PreserveReferencesHandling.Objects,
    Formatting = Formatting.Indented
};

string json = JsonConvert.SerializeObject(employees, settings);

次のJSONを取得します。

[
  {
    "$id": "1",
    "Name": "Angela Anderson",
    "Supervisor": null,
    "Subordinates": [
      {
        "$id": "2",
        "Name": "Bob Brown",
        "Supervisor": {
          "$ref": "1"
        },
        "Subordinates": null
      },
      {
        "$id": "3",
        "Name": "Charles Cooper",
        "Supervisor": {
          "$ref": "1"
        },
        "Subordinates": null
      }
    ]
  },
  {
    "$ref": "2"
  },
  {
    "$ref": "3"
  }
]

JSONで各オブジェクトに$id値が順番に割り当てられていることに注意してください。オブジェクトが最初に表示されると、完全にシリアル化されますが、後続の参照は、対応する$refを持つ元のオブジェクトを参照する特別な$idプロパティに置き換えられます。この設定を行うと、生成される$idおよび$ref表記を理解するライブラリを使用している場合、追加の作業を必要とせずにJSONがより簡潔になり、元のオブジェクト階層にデシリアライズできます。 Json.Net/Web APIによる。

では、なぜあなたはどちらの設定を選択するのでしょうか?もちろん、あなたのニーズ次第です。 JSONが$id/$ref形式を理解しないクライアントによって消費され、場所に不完全なデータが存在することを許容できる場合は、ReferenceLoopHandling.Ignoreを使用することを選択します。よりコンパクトなJSONを探していて、Json.NetまたはWeb API(または別の互換性のあるライブラリ)を使用してデータをデシリアライズする場合、PreserveReferencesHandling.Objectsを使用することを選択します。データが、参照が重複していない有向非巡回グラフである場合、どちらの設定も必要ありません。

133
Brian Rogers