web-dev-qa-db-ja.com

JSON.NETでオブジェクトの関係を無視するにはどうすればよいですか?

私は Entity Framework プロジェクトに取り組んでいます。一連のエンティティークラスインスタンスをシリアル化したい。これらを1つのコンテナークラスにバインドしました。

public class Pseudocontext
{
    public List<Widget> widgets;
    public List<Thing> things;

Etcetera ...シリアル化しようとしているのは、このクラスのインスタンスです。 JSON.NETを使用して、実際のデータベースの列である各エンティティクラスインスタンスのメンバーをシリアル化します。オブジェクト参照のシリアル化を試みたくありません。

特に、私のエンティティクラスには仮想メンバーがあり、実際のキー値や結合などを気にすることなく、エンティティ間のすべての関係をナビゲートするC#コードを記述できます。また、エンティティの関連する部分をJSON.NETに無視させたいです。クラス。

表面的には、私が話していることを正確に実行するJSON.NET構成オプションがあるようです。

JsonSerializer serializer = new JsonSerializer();
serializer.PreserveReferencesHandling = PreserveReferencesHandling.None;

残念ながら、JSON.NETは上記の2番目のステートメントを無視しているようです。

私は実際にWebページ( http://json.codeplex.com/workitem/24608 )を見つけました。このページで、他の誰かが同じ問題をJames Newton-King自身に注目させ、彼の応答(全体)は「カスタムコントラクトリゾルバーを記述する」でした。

私はその対応が不十分だと思うので不十分ですが、私はそのガイダンスに従うように努めています。プリミティブ型、文字列、DateTimeオブジェクト、および自分のPseudocontextクラス以外のすべてを無視する「コントラクトリゾルバー」と、それが直接含むリストを記述できるようになりたいと思います。誰かが少なくともそれに似たものの例を持っている場合、それは私が必要とするすべてのものかもしれません。これは私が自分で思いついたものです:

public class WhatDecadeIsItAgain : DefaultContractResolver
{
    protected override JsonContract CreateContract(Type objectType)
    {
        JsonContract contract = base.CreateContract(objectType);
        if (objectType.IsPrimitive || objectType == typeof(DateTime) || objectType == typeof(string)
            || objectType == typeof(Pseudocontext) || objectType.Name.Contains("List"))
        {
            contract.Converter = base.CreateContract(objectType).Converter;
        }
        else
        {
            contract.Converter = myDefaultConverter;
        }
        return contract;
    }
    private static GeeThisSureTakesALotOfClassesConverter myDefaultConverter = new GeeThisSureTakesALotOfClassesConverter();
}

public class GeeThisSureTakesALotOfClassesConverter : Newtonsoft.Json.Converters.CustomCreationConverter<object>
{
    public override object Create(Type objectType)
    {
        return null;
    }
}

上記を使用しようとすると(シリアライザの前にserializer.ContractResolverをWhatDecadeIsItAgainのインスタンスに設定することにより)、シリアライゼーション中にJSON.NETが終了しない参照ループに遭遇していることを示すOutOfMemoryエラーが発生します( JSON.NETオブジェクト参照を無視する).

「カスタム契約リゾルバ」が間違っているようです。上に示したように、シリアル化したい型に対してはデフォルトの「契約」を返し、他のすべての型に対しては単に「null」を返す「契約」を返すという前提に基づいて構築されています。

しかし、私はこれらの仮定がどれほど正確であるかを知りません、そしてそれを言うのは簡単ではありません。 JSON.NETの設計は、実装の継承、メソッドのオーバーライドなどに非常に基づいています。私はそれほど [〜#〜] oop [〜#〜] の人ではありません、そして私はそのようなデザインはかなりあいまいであると思います。私が実装できる「カスタムコントラクトリゾルバー」インターフェイスがあった場合、 Visual Studio 2012 は必要なメソッドを非常に迅速にスタブ化でき、スタブを次のように埋めるのにほとんど問題がないと思います本当の論理。

たとえば、提供された型のオブジェクトをシリアル化する場合は "true"を返し、それ以外の場合は "false"を返すメソッドを作成しても問題はありません。おそらく何かが足りないかもしれませんが、オーバーライドするそのようなメソッドが見つからなかったし、最後のコードスニペットで実際に何をしているのかを教えてくれる架空のインターフェイス(ICustomContractResolver?)も見つかりませんでした。上に挿入。

また、このような状況に対処するために設計されたJSON.NET属性([JsonIgnore]?)があることも理解しています。私は「モデルファースト」を使用しているため、実際にはそのアプローチを使用できません。プロジェクトアーキテクチャ全体を破棄することを決定しない限り、エンティティクラスは自動的に生成され、JsonIgnore属性は含まれません。また、これらの属性を含むように自動化クラスを編集しても問題ありません。

ちなみに、しばらくの間、私はdidにオブジェクト参照をシリアル化するための設定を行っており、JSON.NETの余分な「$ ref」および「$ id」データをすべて無視していましたシリアライゼーション出力で戻ってきました。 (突然)シリアライゼーションに非常に長い時間がかかり始めたため(〜45分に〜5 MBのJSONを取得するため)、少なくともその時点でこのアプローチを放棄しました。

突然のパフォーマンスの変化を、私が行った特定のことに結びつけることができませんでした。どちらかと言えば、私のデータベースのデータ量は、シリアライゼーションが実際に妥当な時間で完了していたときよりも少なくなっています。しかし、私はstatus quo ante(「$ ref」、「$ id」などを無視する必要があっただけです)に戻って満足します。それが達成できたなら。

この時点で、私は他のJSONライブラリを使用する可能性、またはまったく別の戦略も利用できます。 StringBuilderやSystem.Reflectionなどを使用して独自の自家製のソリューションを作成できると思いますが、JSON.NETはこのようなことをかなり簡単に処理できるはずではありませんか?

28
user1172763

まず、参照ループの問題に対処するために、PreserveReferencesHandling設定はJson.Netが$idおよび$refオブジェクト間参照を追跡します。これをNoneに設定し、オブジェクトグラフにループが含まれる場合、エラーを防ぐためにReferenceLoopHandlingIgnoreに設定する必要もあります。

ここで、Json.Netがすべてのオブジェクト参照を完全に無視し、プリミティブプロパティのみをシリアル化するようにするには(もちろんPseudocontextクラスを除く)、提案したようにカスタムのコントラクトリゾルバーが必要です。しかし、心配しないでください。あなたが考えるほど難しくはありません。リゾルバーには、各プロパティにShouldSerializeメソッドを挿入して、そのプロパティを出力に含めるかどうかを制御する機能があります。したがって、必要なことは、デフォルトのリゾルバを導出し、CreatePropertyを適切に設定するようにShouldSerializeメソッドをオーバーライドすることです。 (ここではカスタムJsonConverterは必要ありませんが、このアプローチでこの問題を解決することは可能です。ただし、かなり多くのコードが必要になります。)

以下はリゾルバのコードです:

class CustomResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty prop = base.CreateProperty(member, memberSerialization);

        if (prop.DeclaringType != typeof(PseudoContext) && 
            prop.PropertyType.IsClass && 
            prop.PropertyType != typeof(string))
        {
            prop.ShouldSerialize = obj => false;
        }

        return prop;
    }
}

以下は、リゾルバーの動作を示す完全なデモです。

class Program
{
    static void Main(string[] args)
    {
        // Set up some dummy data complete with reference loops
        Thing t1 = new Thing { Id = 1, Name = "Flim" };
        Thing t2 = new Thing { Id = 2, Name = "Flam" };

        Widget w1 = new Widget
        {
            Id = 5,
            Name = "Hammer",
            IsActive = true,
            Price = 13.99M,
            Created = new DateTime(2013, 12, 29, 8, 16, 3),
            Color = Color.Red,
        };
        w1.RelatedThings = new List<Thing> { t2 };
        t2.RelatedWidgets = new List<Widget> { w1 };

        Widget w2 = new Widget
        {
            Id = 6,
            Name = "Drill",
            IsActive = true,
            Price = 45.89M,
            Created = new DateTime(2014, 1, 22, 2, 29, 35),
            Color = Color.Blue,
        };
        w2.RelatedThings = new List<Thing> { t1 };
        t1.RelatedWidgets = new List<Widget> { w2 };

        // Here is the container class we wish to serialize
        PseudoContext pc = new PseudoContext
        {
            Things = new List<Thing> { t1, t2 },
            Widgets = new List<Widget> { w1, w2 }
        };

        // Serializer settings
        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.ContractResolver = new CustomResolver();
        settings.PreserveReferencesHandling = PreserveReferencesHandling.None;
        settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
        settings.Formatting = Formatting.Indented;

        // Do the serialization and output to the console
        string json = JsonConvert.SerializeObject(pc, settings);
        Console.WriteLine(json);
    }

    class PseudoContext
    {
        public List<Thing> Things { get; set; }
        public List<Widget> Widgets { get; set; }
    }

    class Thing
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public List<Widget> RelatedWidgets { get; set; }
    }

    class Widget
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public bool IsActive { get; set; }
        public decimal Price { get; set; }
        public DateTime Created { get; set; }
        public Color Color { get; set; }
        public List<Thing> RelatedThings { get; set; }
    }

    enum Color { Red, White, Blue }
}

出力:

{
  "Things": [
    {
      "Id": 1,
      "Name": "Flim"
    },
    {
      "Id": 2,
      "Name": "Flam"
    }
  ],
  "Widgets": [
    {
      "Id": 5,
      "Name": "Hammer",
      "IsActive": true,
      "Price": 13.99,
      "Created": "2013-12-29T08:16:03",
      "Color": 0
    },
    {
      "Id": 6,
      "Name": "Drill",
      "IsActive": true,
      "Price": 45.89,
      "Created": "2014-01-22T02:29:35",
      "Color": 2
    }
  ]
}

これがあなたが探していたものの球場にあることを願っています。

47
Brian Rogers

また、別のメンバータイプ名(forを持つすべてのモデルクラスに対してこれを行う方法を探している場合)たとえば、Entity Frameworkによって作成されたいくつかのモデルがありますこの答えが役立ち、ナビゲーションを無視できますそれによるJSONシリアル化のプロパティ。

6
RAM

より簡単な方法は、モデルのT4テンプレート(.tt)を変更して、ナビゲーションプロパティに[JsonIgnore]属性を追加することです。これにより、プリミティブ型はシリアル化可能のままになります。

4
LMK