【问题标题】:How do I make JSON.NET ignore object relationships?如何让 JSON.NET 忽略对象关系?
【发布时间】:2014-02-13 01:07:39
【问题描述】:

我正在处理一个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 对象和我自己的 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 的实例),我在序列化期间收到 OutOfMemory 错误,表明 JSON.NET 遇到了永远不会终止的引用循环(尽管我努力使 JSON.NET忽略对象引用)。

我觉得我的“自定义合同解析器”可能有误。如上所示,它是围绕这样一个前提构建的,即我应该为我想要序列化的类型返回默认的“合同”,以及为所有其他类型简单地返回“null”的“合同”。

不过,我不知道这些假设有多正确,也很难说清楚。 JSON.NET 设计非常基于实现继承、方法覆盖等;我不是一个OOP 的人,我发现这种设计非常晦涩难懂。如果有我可以实现的“自定义合同解析器”接口,Visual Studio 2012 将能够非常快速地存根出所需的方法,而且我想用真实的逻辑填充存根不会有什么问题。

例如,如果我想序列化所提供类型的对象,则返回“true”,否则返回“false”,我可以毫无问题地编写。也许我遗漏了一些东西,但我没有发现这样的方法可以覆盖,也找不到假设的接口(ICustomContractResolver?),它可以告诉我在最后一个代码中我实际上应该做什么-p 在上面插入。

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

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

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

此时,我也对使用其他 JSON 库或完全不同的策略持开放态度。我觉得我可以只使用 StringBuilder、System.Reflection 等,并使用我自己的自制解决方案......但是 JSON.NET 不应该能够很容易地处理这种事情吗??

【问题讨论】:

    标签: c# .net json serialization json.net


    【解决方案1】:

    首先,解决引用循环的问题——PreserveReferencesHandling 设置控制 Json.Net 是否发出 $id$ref 以跟踪对象间引用。如果您将此设置为 None 并且您的对象图包含循环,那么您还需要将 ReferenceLoopHandling 设置为 Ignore 以防止错误。

    现在,要让 Json.Net 完全忽略所有对象引用并仅序列化原始属性(当然在您的 Pseudocontext 类中除外),您确实需要一个自定义的 Contract Resolver,正如您所建议的那样。不过别担心,它并没有你想象的那么难。解析器能够为每个属性注入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
        }
      ]
    }
    

    希望这与您所寻找的差不多。

    【讨论】:

    • 是的,这完全解释了事情。您的 CreateProperty() 实现尤其是我所缺少的。其余的很容易偶然发现。总体结果是一个非常有用的示例。
    • 谢谢,很高兴你发现它有用。
    • 这真的让我不必装饰模型中的所有属性,非常感谢!
    • 嗨@BrianRogers 它对我有用。但是我在 CustomResolver 类中找不到 prop.DeclaringType != typeof(PseudoContext) 所以我只是把它注释掉了。
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 非常感谢!我去了我的模型并使用了属性和 vualá!正如所选答案中所建议的那样,这似乎是一个非常简单且有用的解决方案来创建我们自己的解决方案。
      猜你喜欢
      • 2016-09-15
      • 2017-01-27
      • 1970-01-01
      • 2021-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-25
      相关资源
      最近更新 更多