如何生成JSON。净忽视对象关系?

时间:2022-02-05 07:20:04

I'm working on an Entity Framework project. I want to serialize a bunch of entity class instances. I've bound these together into a container class:

我正在研究一个实体框架项目。我想序列化一堆实体类实例。我将它们绑定到一个容器类中:

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

Etcetera... it is an instance of this class that I'm attempting to serialize. I want JSON.NET to serialize the members of each entity class instance that are actually columns in the underlying database. I do not want it to even attempt to serialize object references.

等等……它是我试图序列化的类的一个实例。我希望JSON。NET序列化每个实体类实例的成员,这些实例实际上是底层数据库中的列。我甚至不希望它尝试序列化对象引用。

In particular, my entity classes have virtual members that allow me to write C# code that navigates all my inter-entity relationships without worrying about actual key values, joins, etc., and I want JSON.NET to ignore the associated parts of my entity classes.

特别是,我的实体类有虚拟成员,这些成员允许我编写c#代码来导航我的所有实体间关系,而不必担心实际的键值、连接等等,我想要JSON。忽略实体类的关联部分。

On the surface, there seems to be a JSON.NET configuration option that does exactly what I'm talking about:

从表面上看,似乎有一个JSON。NET配置选项,它和我说的完全一样:

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

Unfortunately, JSON.NET seems to be ignoring the second statement above.

不幸的是,JSON。NET似乎忽略了上面的第二个语句。

I actually found a web page (http://json.codeplex.com/workitem/24608) where someone else brought the same issue to the attention of James Newton-King himself, and his response (in its entirety) was "Write a custom contract resolver."

实际上,我发现了一个网页(http://json.codeplex.com/workitem/24608),在这个网页上,有人向詹姆斯·牛顿-金(James Newton-King)提出了同样的问题。

As inadequate as I find that response to be, I have been attempting to follow its guidance. I would very much like to be able to write a "contract resolver" that ignored everything except primitive types, strings, DateTime objects, and my own Pseudocontext class along with the Lists it contains directly. If someone has an example of something that at least resembles that, it might be all I need. This is what I came up with on my own:

尽管我觉得这种反应不够充分,但我一直试图遵循它的指导。我非常希望能够编写一个“契约解析器”,它忽略除了基本类型、字符串、DateTime对象和我自己的伪上下文类以及它直接包含的列表之外的所有内容。如果某人有一个至少类似的例子,那可能就是我所需要的。这是我自己想到的:

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;
    }
}

When I attempt to use the above (by setting serializer.ContractResolver to an instance of WhatDecadeIsItAgain prior to serialization), I get OutOfMemory errors during serialization that indicate that JSON.NET is encountering reference loops that never terminate (in spite of my efforts to make JSON.NET just ignore object references).

当我尝试使用上面的(通过设置serializer)。在序列化之前,再一次将ContractResolver转换为what车的实例),在序列化期间,我将得到OutOfMemory错误,这些错误指示JSON。NET遇到了永不终止的引用循环(尽管我努力使JSON成为JSON)。NET只忽略对象引用。

I feel like my "custom contract resolver" may be wrong. As shown above, it's built around the premise that I should return the default "contract" for the types I do want to serialize, and a "contract" that simply returns "null" for all other types.

我觉得我的“自定义合同解析器”可能是错误的。如上所示,它是基于以下前提构建的:我应该为我确实想要序列化的类型返回默认的“契约”,以及一个“契约”,它只是为所有其他类型返回“null”。

I have no idea how correct these assumptions are, though, and it's not easy to tell. The JSON.NET design is very much based on implementation inheritance, method overriding, etc.; I'm not much of an OOP guy, and I find that sort of design to be pretty obscure. Were there a "custom contract resolver" interface that I could implement, Visual Studio 2012 would be able to stub out the required methods very quickly, and I imagine I'd have little trouble filling the stubs in with real logic.

然而,我不知道这些假设有多正确,而且很难说。JSON。NET设计很大程度上是基于实现继承、方法重写等;我不太喜欢OOP,而且我觉得那种设计很晦涩。如果我可以实现一个“自定义契约解析器”接口,Visual Studio 2012将能够很快地排除所需的方法,我想我将会在用真正的逻辑填充存根时遇到一些麻烦。

I'd have no problem writing, for example, a method that returns "true" if I want to serialize an object of a supplied type and "false" otherwise. Perhaps I'm missing something, but I've found no such method to override, nor have I been able to find the hypothetical interface (ICustomContractResolver ?) that would tell me what I'm actually supposed to be doing in the last code snippet inserted above.

例如,如果我想要序列化一个提供类型的对象,而不是“false”,那么编写一个返回“true”的方法是没有问题的。也许我漏掉了什么,但是我没有找到这样的方法来重写,也没有找到假设的接口(ICustomContractResolver ?),它可以告诉我在上面插入的最后一个代码片段中应该做什么。

Also, I realize that there are JSON.NET attributes ([JsonIgnore]?) that are designed to deal with situations like this. I can't really use that approach, since I'm using "model first". Unless I decide to tear up my entire project architecture, my entity classes will be automatically generated, and they will not contain JsonIgnore attributes, nor do I feel comfortable editing the automated classes to contain these attributes.

而且,我意识到有JSON。NET属性([JsonIgnore]?)用于处理这种情况。我不能用那种方法,因为我用的是“模型优先”。除非我决定破坏整个项目体系结构,否则我的实体类将自动生成,它们不会包含JsonIgnore属性,我也不愿意编辑自动类来包含这些属性。

Incidentally, for a while I did have things set up to serialize object references, and I was just ignoring all the superfluous "$ref" and "$id" data that JSON.NET was returning in its serialization output. I've abandoned that approach for the moment at least, because (rather suddenly) serialization started taking an inordinate amount of time (~45 minutes to get ~5 MB of JSON).

顺便说一句,有一段时间我确实设置了一些东西来序列化对象引用,我只是忽略了JSON中所有多余的“$ref”和“$id”数据。NET正在返回它的序列化输出。至少我暂时放弃了这种方法,因为(相当突然地)序列化开始花费过多的时间(大约45分钟得到大约5 MB的JSON)。

I haven't been able to tie that sudden change in performance back to anything specific that I did. If anything, the volume of data in my database is lower now than it was when serialization was actually completing in reasonable time. But I'd be more than happy with a return to the status quo ante (in which I was just having to ignore "$ref", "$id", etc.) if that could be achieved.

我还没能将这种突然的性能变化与我所做的事情联系起来。如果有什么不同的话,我的数据库中的数据量现在比序列化在合理时间内完成时要低。但如果可以实现的话,我很乐意回到之前的状态(在这种状态下,我不得不忽略“$ref”、“$id”等等)。

At this point, I'm also open to the prospect of using some other JSON library, or a different strategy altogether. I feel like I could just use StringBuilder, System.Reflection, etc. and come of with my own, homemade solution... but isn't JSON.NET supposed to be able to handle this sort of thing pretty easily??

现在,我还对使用其他JSON库或完全不同的策略持开放态度。我觉得我可以用StringBuilder系统。反思等等,还有我自己自制的解决方案……但不是JSON。NET应该能够很容易地处理这类事情吗?

3 个解决方案

#1


42  

First, to address your issues with reference loops-- The PreserveReferencesHandling setting controls whether Json.Net emits $id and $ref to track inter-object references. If you have this set to None and your object graph contains loops, then you will also need to set ReferenceLoopHandling to Ignore to prevent errors.

首先,要使用引用循环解决问题——PreserveReferencesHandling设置来控制Json。Net发出$id和$ref来跟踪对象间引用。如果设置为None并且对象图包含循环,那么还需要设置ReferenceLoopHandling以忽略它以防止错误。

Now, to get Json.Net to ignore all object references altogether and only serialize primitive properties (except in your Pseudocontext class of course), you do need a custom Contract Resolver, as you suggested. But don't worry, it is not as hard as you think. The resolver has the capability to inject a ShouldSerialize method for each property to control whether or not that property should be included in the output. So, all you need to do is derive your resolver from the default one, then override the CreateProperty method such that it sets ShouldSerialize appropriately. (You do not need a custom JsonConverter here, although it is possible to solve this problem with that approach albeit with quite a bit more code.)

现在,Json。Net要完全忽略所有对象引用,并且只序列化原始属性(当然除了伪上下文类),您确实需要一个自定义契约解析器,正如您所建议的那样。但别担心,这并不像你想象的那么难。解析器可以为每个属性注入一个ShouldSerialize方法,以控制该属性是否应该包含在输出中。因此,您需要做的就是从默认的解析器中派生解析器,然后重写CreateProperty方法,使其能够适当地设置ShouldSerialize。(这里不需要自定义JsonConverter,尽管使用这种方法可以解决这个问题,尽管需要更多的代码)。

Here is the code for the resolver:

这是解析器的代码:

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;
    }
}

Here is a full demo showing the resolver in action.

这里有一个完整的演示,展示了正在运行的解析器。

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 }
}

Output:

输出:

{
  "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
    }
  ]
}

Hope this is in the ballpark of what you were looking for.

希望这是你想要的。

#2


3  

An easier method is to modify your model T4 template (.tt) to append JSONIgnore attributes to navigation properties, which will just leave the primitive types as serializable.

一个更简单的方法是修改您的model T4模板(.tt),将JSONIgnore属性附加到导航属性中,这将使原始类型成为可序列化的。

#3


3  

Also, if you are looking for a way to do this for all of your model classes with different member type names (for example, you have some models created by Entity Framework) this answer can help and you can ignore navigation properties in JSON serialization by it.

此外,如果您正在寻找一种方法来为具有不同成员类型名称的所有模型类(例如,您有一些由实体框架创建的模型)实现这一点,那么这个答案将会有所帮助,并且您可以通过它忽略JSON序列化中的导航属性。

#1


42  

First, to address your issues with reference loops-- The PreserveReferencesHandling setting controls whether Json.Net emits $id and $ref to track inter-object references. If you have this set to None and your object graph contains loops, then you will also need to set ReferenceLoopHandling to Ignore to prevent errors.

首先,要使用引用循环解决问题——PreserveReferencesHandling设置来控制Json。Net发出$id和$ref来跟踪对象间引用。如果设置为None并且对象图包含循环,那么还需要设置ReferenceLoopHandling以忽略它以防止错误。

Now, to get Json.Net to ignore all object references altogether and only serialize primitive properties (except in your Pseudocontext class of course), you do need a custom Contract Resolver, as you suggested. But don't worry, it is not as hard as you think. The resolver has the capability to inject a ShouldSerialize method for each property to control whether or not that property should be included in the output. So, all you need to do is derive your resolver from the default one, then override the CreateProperty method such that it sets ShouldSerialize appropriately. (You do not need a custom JsonConverter here, although it is possible to solve this problem with that approach albeit with quite a bit more code.)

现在,Json。Net要完全忽略所有对象引用,并且只序列化原始属性(当然除了伪上下文类),您确实需要一个自定义契约解析器,正如您所建议的那样。但别担心,这并不像你想象的那么难。解析器可以为每个属性注入一个ShouldSerialize方法,以控制该属性是否应该包含在输出中。因此,您需要做的就是从默认的解析器中派生解析器,然后重写CreateProperty方法,使其能够适当地设置ShouldSerialize。(这里不需要自定义JsonConverter,尽管使用这种方法可以解决这个问题,尽管需要更多的代码)。

Here is the code for the resolver:

这是解析器的代码:

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;
    }
}

Here is a full demo showing the resolver in action.

这里有一个完整的演示,展示了正在运行的解析器。

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 }
}

Output:

输出:

{
  "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
    }
  ]
}

Hope this is in the ballpark of what you were looking for.

希望这是你想要的。

#2


3  

An easier method is to modify your model T4 template (.tt) to append JSONIgnore attributes to navigation properties, which will just leave the primitive types as serializable.

一个更简单的方法是修改您的model T4模板(.tt),将JSONIgnore属性附加到导航属性中,这将使原始类型成为可序列化的。

#3


3  

Also, if you are looking for a way to do this for all of your model classes with different member type names (for example, you have some models created by Entity Framework) this answer can help and you can ignore navigation properties in JSON serialization by it.

此外,如果您正在寻找一种方法来为具有不同成员类型名称的所有模型类(例如,您有一些由实体框架创建的模型)实现这一点,那么这个答案将会有所帮助,并且您可以通过它忽略JSON序列化中的导航属性。