如何使JSON.NET忽略对象关系?

use*_*763 28 .net c# serialization json json.net

我正在开发一个Entity Framework项目.我想序列化一堆实体类实例.我将它们绑定到一个容器类中:

public class Pseudocontext
{
    public List<Widget> widgets;
    public List<Thing> things;
Run Code Online (Sandbox Code Playgroud)

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

特别是,我的实体类有虚拟成员,允许我编写C#代码,导航我所有的实体间关系,而不用担心实际的键值,连接等,我希望JSON.NET忽略我的实体的相关部分类.

从表面上看,似乎有一个JSON.NET配置选项正是我所说的:

JsonSerializer serializer = new JsonSerializer();
serializer.PreserveReferencesHandling = PreserveReferencesHandling.None;
Run Code Online (Sandbox Code Playgroud)

不幸的是,JSON.NET似乎忽略了上面的第二个声明.

我实际上找到了一个网页(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;
    }
}
Run Code Online (Sandbox Code Playgroud)

当我尝试使用上面的内容时(通过在序列化之前将serializer.ContractResolver设置为WhatDecadeIsItAgain的实例),我在序列化期间出现OutOfMemory错误,表明JSON.NET遇到永不终止的引用循环(尽管我努力制作JSON.NET 只是忽略对象引用).

我觉得我的"自定义合同解析器"可能是错的.如上所示,它的前提是我应该返回我想要序列化的类型的默认"契约",以及一个简单地为所有其他类型返回"null"的"契约".

我不知道这些假设有多正确,但这并不容易.JSON.NET设计非常基于实现继承,方法覆盖等; 我不是一个OOP人,我觉得那种设计很模糊.如果有一个我可以实现的"自定义合约解析器"界面,Visual Studio 2012将能够非常快速地删除所需的方法,并且我想我在使用真实逻辑填充存根时几乎没有问题.

我编写没有问题,例如,如果我想序列化所提供类型的对象,则返回"true"的方法,否则返回"false".也许我错过了一些东西,但我发现没有这样的方法可以覆盖,也没有找到假设的界面(ICustomContractResolver?),它会告诉我在最后一段代码片段中我应该做些什么插在上面.

另外,我意识到有JSON.NET属性([JsonIgnore]?)旨在处理这样的情况.我不能真正使用这种方法,因为我正在使用"模型优先".除非我决定拆除整个项目架构,否则我的实体类将自动生成,它们不会包含JsonIgnore属性,也不习惯编辑自动类来包含这些属性.

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

我无法将性能的突然变化与我所做的任何具体事情联系起来.如果有的话,我的数据库中的数据量现在低于序列化在合理时间内实际完成时的数据量.但是,如果能够实现这一目标,我会非常高兴回到现状(我只需要忽略"$ ref","$ id"等).

在这一点上,我也对使用其他JSON库或完全不同的策略的前景持开放态度.我觉得我可以使用StringBuilder,System.Reflection等来使用我自己的,自制的解决方案...但是JSON.NET不应该能够很容易地处理这类事情吗?

Bri*_*ers 47

首先,使用引用循环来解决您的问题 - 该PreserveReferencesHandling设置控制Json.Net是否发出$id以及$ref跟踪对象间引用.如果你有这一套来None和你的对象图包含循环,那么你也将需要设置ReferenceLoopHandling,以Ignore防止发生错误.

现在,为了让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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个完整的演示,显示了解析器的运行情况.

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 }
}
Run Code Online (Sandbox Code Playgroud)

输出:

{
  "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
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

希望这是你正在寻找的球场.

  • 这真的让我不必装饰我模型中的所有属性,非常感谢! (2认同)

RAM*_*RAM 6

此外,如果您正在寻找一种方法来为所有具有不同成员类型名称的模型类执行此操作(例如,您有一些由Entity Framework创建的模型),则此答案可以提供帮助,您可以忽略JSON序列化中的导航属性.它.


LMK*_*LMK 5

一种更简单的方法是修改您的模型 T4 模板 (.tt) 以将[JsonIgnore]属性附加到您的导航属性,这只会将基本类型保留为可序列化。