【问题标题】:JSON.NET serialize JObject while ignoring null propertiesJSON.NET 序列化 JObject 而忽略空属性
【发布时间】:2015-10-08 23:09:04
【问题描述】:

我有一个JObject,用作调用 RESTful Web 服务的模板。这个JObject 是通过解析器创建的,因为它被用作告诉用户端点架构是什么样子的模板,所以我必须想办法保留所有属性,这就是为什么我将它们的值默认为@987654324 @。例如,这是对象最初的样子:

{  
   "Foo":{  
      "P1":null,
      "P2":null,
      "P3":null,
      "P4":{  
         "P1":null,
         "P2":null,
         "P3":null,
      },
      "FooArray":[  
         {  
            "F1":null,
            "F2":null,
            "F3":null,
         }
      ]
   },
   "Bar":null
}

然后用户可以根据需要填写各个字段,例如Foo.P2 和Foo.P4.P1:

{  
   "Foo":{  
      "P1":null,
      "P2":"hello world",
      "P3":null,
      "P4":{  
         "P1":1,
         "P2":null,
         "P3":null,
      },
      "FooArray":[  
         {  
            "F1":null,
            "F2":null,
            "F3":null,
         }
      ]
   },
   "Bar":null
}

意味着他们只关心这两个领域。现在我想将此模板 (JObject) 序列化回 JSON 字符串,但只希望显示那些已填充的字段。所以我尝试了这个:

string json = JsonConvert.SerializeObject(template,
    new JsonSerializerSettings
    {
        NullValueHandling = NullValueHandling.Ignore
    });

很遗憾,这不起作用。我遇到this question 并意识到对象中的null 值是实际的JToken 类型,而不是真正的null,这是有道理的。但是,在这种非常特殊的情况下,我需要能够摆脱这些“未使用”的字段。我尝试手动迭代节点并删除它们,但这也不起作用。请注意,我使用的唯一托管类型是JObject;我没有将对象转换为或定义属性的模型,因为此“模板”在运行时被解析。我只是想知道是否有人遇到过这样的问题并有任何见解。非常感谢任何帮助!

【问题讨论】:

    标签: c# json json.net nsjsonserialization


    【解决方案1】:

    您可以使用如下所示的递归辅助方法在序列化之前从JToken 层次结构中删除null 值。

    using System;
    using Newtonsoft.Json.Linq;
    
    public static class JsonHelper
    {
        public static JToken RemoveEmptyChildren(JToken token)
        {
            if (token.Type == JTokenType.Object)
            {
                JObject copy = new JObject();
                foreach (JProperty prop in token.Children<JProperty>())
                {
                    JToken child = prop.Value;
                    if (child.HasValues)
                    {
                        child = RemoveEmptyChildren(child);
                    }
                    if (!IsEmpty(child))
                    {
                        copy.Add(prop.Name, child);
                    }
                }
                return copy;
            }
            else if (token.Type == JTokenType.Array)
            {
                JArray copy = new JArray();
                foreach (JToken item in token.Children())
                {
                    JToken child = item;
                    if (child.HasValues)
                    {
                        child = RemoveEmptyChildren(child);
                    }
                    if (!IsEmpty(child))
                    {
                        copy.Add(child);
                    }
                }
                return copy;
            }
            return token;
        }
    
        public static bool IsEmpty(JToken token)
        {
            return (token.Type == JTokenType.Null);
        }
    }
    

    演示:

    string json = @"
    {
        ""Foo"": {
            ""P1"": null,
            ""P2"": ""hello world"",
            ""P3"": null,
            ""P4"": {
                ""P1"": 1,
                ""P2"": null,
                ""P3"": null
            },
            ""FooArray"": [
                {
                    ""F1"": null,
                    ""F2"": null,
                    ""F3"": null
                }
            ]
        },
        ""Bar"": null
    }";
    
    JToken token = JsonHelper.RemoveEmptyChildren(JToken.Parse(json));
    Console.WriteLine(token.ToString(Formatting.Indented));
    

    输出:

    {
      "Foo": {
        "P2": "hello world",
        "P4": {
          "P1": 1
        },
        "FooArray": [
          {}
        ]
      }
    }
    

    小提琴:https://dotnetfiddle.net/wzEOie

    请注意,删除所有空值后,FooArray 中将有一个空对象,这可能是您不想要的。 (如果该对象被删除,那么您将有一个空的FooArray,您也可能不想要它。)如果您想让辅助方法在删除时更具侵略性,您可以将 IsEmpty 函数更改为:

        public static bool IsEmpty(JToken token)
        {
            return (token.Type == JTokenType.Null) ||
                   (token.Type == JTokenType.Array && !token.HasValues) ||
                   (token.Type == JTokenType.Object && !token.HasValues);
        }
    

    进行该更改后,您的输出将如下所示:

    {
      "Foo": {
        "P2": "hello world",
        "P4": {
          "P1": 1
        }
      }
    }
    

    小提琴:https://dotnetfiddle.net/ZdYogJ

    【讨论】:

    • 谢谢!我在发布问题后不久想出了一个几乎相似的算法,但你的答案仍然有效:)
    • 没问题;很高兴我能帮上忙。
    • 我一直在努力实现这一目标,感谢您的贡献,它有效!
    • 感谢这个解决方案。迄今为止我发现的最好的。
    【解决方案2】:

    您可以通过指定JsonSerializer 并将其NullValueHandler 设置为NullValueHandler.Ignore 来防止创建空标记。这作为参数传递给JObject.FromObject,如您链接到的同一问题的答案所示:https://stackoverflow.com/a/29259032/263139。

    【讨论】:

      【解决方案3】:

      布赖恩的回答有效。在发布问题后不久,我还提出了另一种(但仍然是递归的)方法,以防其他人感兴趣。

      private void RemoveNullNodes(JToken root)
      {
          if (root is JValue)
          {
              if (((JValue)root).Value == null)
              {
                  ((JValue)root).Parent.Remove();
              }
          }
          else if (root is JArray)
          {
              ((JArray)root).ToList().ForEach(n => RemoveNullNodes(n));
              if (!(((JArray)root)).HasValues)
              {
                  root.Parent.Remove();
              }
          }
          else if (root is JProperty)
          {
              RemoveNullNodes(((JProperty)root).Value);
          }
          else
          {
              var children = ((JObject)root).Properties().ToList();
              children.ForEach(n => RemoveNullNodes(n));
      
              if (!((JObject)root).HasValues)
              {
                  if (((JObject)root).Parent is JArray)
                  {
                      ((JArray)root.Parent).Where(x => !x.HasValues).ToList().ForEach(n => n.Remove());
                  }
                  else
                  {
                      var propertyParent = ((JObject)root).Parent;
                      while (!(propertyParent is JProperty))
                      {
                          propertyParent = propertyParent.Parent;
                      }
                      propertyParent.Remove();
                  }
              }
          }
      }
      

      【讨论】:

        【解决方案4】:

        这是我能想到的。它删除仅包含空值的属性。这意味着它将处理属性为 null 的标量值的情况,并且还将处理存在所有为 null 值的数组的情况。它还会删除没有值的属性。这可以处理属性包含没有子属性的对象的情况。请注意,我的使用 JObject 有一个 Descendents() 方法,这使实现变得容易。 JToken 没有。我的实现改变了JObject 本身,而不是创建它的副本。此外,它会继续删除属性,直到不再出现。它比其他实现更简洁。我不知道它如何比较性能。

        using Newtonsoft.Json.Linq;
        using System;
        using System.IO;
        using System.Linq;
        
        namespace JsonConsoleApplication
        {
            class Program
            {
                static void Main(string[] args)
                {
                    var jo = JObject.Parse(File.ReadAllText(@"test.json"));
                    Console.WriteLine($"BEFORE:\r\n{jo}");
                    jo.RemoveNullAndEmptyProperties();
                    Console.WriteLine($"AFTER:\r\n{jo}");
                }
            }
        
            public static class JObjectExtensions
            {
                public static JObject RemoveNullAndEmptyProperties(this JObject jObject)
                {
                    while (jObject.Descendants().Any(jt => jt.Type == JTokenType.Property && (jt.Values().All(a => a.Type == JTokenType.Null) || !jt.Values().Any())))
                        foreach (var jt in jObject.Descendants().Where(jt => jt.Type == JTokenType.Property && (jt.Values().All(a => a.Type == JTokenType.Null) || !jt.Values().Any())).ToArray())
                            jt.Remove();
                    return jObject;
                }
            }
        }
        

        以下是程序输出:

        BEFORE:
        {
          "propertyWithValue": "",
          "propertyWithObjectWithProperties": {
            "nestedPropertyWithValue": "",
            "nestedPropertyWithNull": null
          },
          "propertyWithEmptyObject": {},
          "propertyWithObjectWithPropertyWithNull": {
            "nestedPropertyWithNull": null
          },
          "propertyWithNull": null,
          "emptyArray": [],
          "arrayWithNulls": [
            null,
            null
          ],
          "arrayWithObjects": [
            {
              "propertyWithValue": ""
            },
            {
              "propertyWithNull": null
            }
          ]
        }
        AFTER:
        {
          "propertyWithValue": "",
          "propertyWithObjectWithProperties": {
            "nestedPropertyWithValue": ""
          },
          "arrayWithObjects": [
            {
              "propertyWithValue": ""
            },
            {}
          ]
        }
        

        【讨论】:

          猜你喜欢
          • 2016-08-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-05-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多