JSON.NET在忽略空属性

时间:2015-10-08 23:09:04

标签: c# json json.net nsjsonserialization

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

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

然后,用户可以根据需要填写各个字段,例如Foo.P2Foo.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;我没有将对象转换为或定义属性的模型,因为此“模板”在运行时得到解析。我只是想知道是否有人遇到过这样的问题并且有任何见解。非常感谢任何帮助!

4 个答案:

答案 0 :(得分:17)

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

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

答案 1 :(得分:2)

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

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

答案 2 :(得分:1)

这就是我能想到的。它删除仅包含空值的属性。这意味着它将处理该属性为标量值为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": ""
    },
    {}
  ]
}

答案 3 :(得分:0)

您可以通过指定JsonSerializer并将NullValueHandler设置为NullValueHandler.Ignore来阻止创建空令牌。这将作为参数传递给JObject.FromObject,如您对链接到的相同问题的回答中所示:https://stackoverflow.com/a/29259032/263139