JSON.NET序列化JObject,同时忽略null属性



我有一个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
    });

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

在序列化JToken层次结构之前,您可以使用类似下面这样的递归助手方法从CCD_12层级结构中删除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": [
      {}
    ]
  }
}

Fiddle:https://dotnetfiddle.net/wzEOie

请注意,在删除所有null值后,FooArray中会有一个空对象,这可能是您不想要的。(如果该对象被删除,那么你会有一个空的FooArray,你可能也不想要。(如果你想让helper方法在删除时更积极,你可以将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
    }
  }
}

Fiddle:https://dotnetfiddle.net/ZdYogJ

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

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

使用JsonPath,我们可以获得更优雅的解决方案:

 jObject.SelectTokens("$..*")
            .OfType<JValue>()
            .Where(x=>x.Type == JTokenType.Null)
            .Select(a => a.Parent)
            .ToList()
            .ForEach(a => a.Remove());

这里有一个工作示例:https://dotnetfiddle.net/zVgXOq

以下是我能想到的。它删除只包含null值的属性。这意味着它将处理属性为null的标量值的情况,还将处理数组全部为null的情况。它还会删除没有值的特性。这将处理属性包含没有子属性的对象的情况。注意,我使用了一个JObject,它有一个CCD21方法,这使得实现变得容易。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:rn{jo}");
            jo.RemoveNullAndEmptyProperties();
            Console.WriteLine($"AFTER:rn{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": ""
    },
    {}
  ]
}

相关内容

  • 没有找到相关文章

最新更新