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

6yt4nkrj  于 2023-03-20  发布在  .NET
关注(0)|答案(4)|浏览(135)

我正在做一个Entity Framework项目,我想序列化一堆实体类示例,我把它们绑定到一个容器类中:

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

等等......我试图序列化的是这个类的一个示例。我希望JSON.NET序列化每个实体类示例的成员,这些成员实际上是底层数据库中的列。我甚至不希望它试图序列化对象引用。
特别是,我的实体类具有虚拟成员,这些成员允许我编写C#代码来导航所有实体间关系,而无需担心实际的键值、连接等,并且我希望JSON .NET忽略实体类的关联部分。
从表面上看,似乎有一个JSON .NET配置选项可以做我所说的事情:

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

不幸的是,JSON .NET似乎忽略了上面的第二条语句。
实际上,我发现了一个网页(http://json.codeplex.com/workitem/24608),在那里有人向James Newton-King本人提出了同样的问题,他的回答(完整地)是“编写一个自定义契约解析器”。
尽管我觉得这个回答不够充分,但我一直在尝试遵循它的指导。我非常希望能够编写一个“契约解析器”,它忽略除了基元类型、字符串、DateTime对象和我自己的伪上下文类沿着它直接包含的List之外的所有内容。如果有人有一个至少类似于此的示例,这可能就是我所需要的。这是我自己想出来的:

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的示例)时,我在序列化期间收到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不应该能够很容易地处理这类事情吗?

irlmq6kh

irlmq6kh1#

首先,解决引用循环的问题--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;
    }
}

这里是一个完整的演示,展示了解决方案的行动。

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

希望这是在你要找的东西的范围内。

e4yzc0pl

e4yzc0pl2#

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

btqmn9zl

btqmn9zl3#

一个更简单的方法是修改模型T4模板(.tt),将[JsonIgnore]属性附加到导航属性,这将使原始类型保持可序列化。

carvr3hs

carvr3hs4#

还有另一个简单的解决方案,将下面这行代码添加到DbContext构造函数中:

public MyContextEntities() : base("name=MyContextEntities")
    {
        Configuration.ProxyCreationEnabled = false;
    }

JsonConvert不会序列化System.Data.Entity.DynamicProxies.* 命名空间中的所有内容。实际上,您将得到一个json,其中只填充您在Include()中添加的依赖项。

相关问题