简体   繁体   English

JSON.NET 序列化 JObject 而忽略空属性

[英]JSON.NET serialize JObject while ignoring null properties

I have a JObject which is used as a template for calling RESTful web services.我有一个JObject用作​​调用 RESTful Web 服务的模板 This JObject gets created via a parser and since it's used as a template telling the user what the endpoint schema looks like, I had to figure out a way to preserve all properties, which is why I'm defaulting their values to null .这个JObject是通过解析器创建的,因为它被用作告诉用户端点模式是什么样子的模板,所以我必须想办法保留所有属性,这就是我将它们的值默认为null的原因。 As as example, this is what the object originally looks like:例如,这是对象最初的样子:

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

The user is then able to fill in individual fields as they need, such as Foo.P2 and Foo.P4.P1 :然后,用户可以根据需要填写各个字段,例如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
}

meaning they only care about those two fields.这意味着他们只关心这两个领域。 Now I want to serialize this template ( JObject ) back to a JSON string, but want only those fields that are populated to show up.现在我想将此模板 ( JObject ) 序列化回 JSON 字符串,但只希望显示那些填充的字段。 So I tried this:所以我尝试了这个:

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

Unfortunately, this didn't work.不幸的是,这没有奏效。 I came across this question and realized that a null value in the object is an actual JToken type and not really a null , which makes sense.我遇到了这个问题,并意识到对象中的null值是实际的JToken类型,而不是真正的null ,这是有道理的。 However, in this very particular case, I need to be able to get rid of these "unused" fields.但是,在这种非常特殊的情况下,我需要能够摆脱这些“未使用”的字段。 I tried manually iterating over nodes and removing them but that didn't work either.我尝试手动迭代节点并删除它们,但这也不起作用。 Note that the only managed type I'm using is JObject ;请注意,我使用的唯一托管类型是JObject I don't have a model to convert the object to or define attributes on, since this "template" gets resolved at runtime.我没有将对象转换为或定义属性的模型,因为此“模板”在运行时被解析。 I was just wondering if anyone has encountered a problem like this and has any insights.我只是想知道是否有人遇到过这样的问题并有任何见解。 Any help is greatly appreciated!任何帮助是极大的赞赏!

You can use a recursive helper method like the one below to remove the null values from your JToken hierarchy prior to serializing it.您可以使用如下所示的递归辅助方法在序列化之前从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);
    }
}

Demo:演示:

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

Output:输出:

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

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

Notice that, after removing all null values, you will have an empty object in the FooArray , which you may not want.请注意,在删除所有空值之后,您将在FooArray中有一个空对象,这可能是您不想要的。 (And if that object were removed, then you'd have an empty FooArray , which you also may not want.) If you want to make the helper method more aggressive in its removal, you can change the IsEmpty function to this: (如果该对象被删除,那么您将有一个空的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);
    }

With that change in place, your output would look like this instead:进行该更改后,您的输出将如下所示:

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

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

You can prevent the null tokens from being created to begin with by specifying the JsonSerializer with its NullValueHandler set to NullValueHandler.Ignore .您可以通过指定JsonSerializer并将其NullValueHandler设置为NullValueHandler.Ignore来防止创建空标记。 This is passed in as a parameter to JObject.FromObject as seen in an answer to the same question you linked to: https://stackoverflow.com/a/29259032/263139 .这作为参数传递给JObject.FromObject ,如您链接到的同一问题的答案所示: https ://stackoverflow.com/a/29259032/263139。

Brian's answer works.布赖恩的回答有效。 I also came up with another (yet still recursive) way of doing it shortly after posting the question, in case anyone else is interested.在发布问题后不久,我还提出了另一种(但仍然是递归的)方法,以防其他人感兴趣。

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

Here's what I was able to come up with.这就是我能想到的。 It removes properties that contain only null values.它删除仅包含空值的属性。 This means that it will handle the case where the property is a scalar value that is null and will also handle the case where there is an array that is all null values.这意味着它将处理属性为 null 的标量值的情况,并且还将处理存在所有为 null 值的数组的情况。 It also removes properties that have no values.它还会删除没有值的属性。 This handles the case where the property contains an object that has no child properties.这可以处理属性包含没有子属性的对象的情况。 Note, mine uses a JObject which has a Descendents() method which is what made the implementation easy.请注意,我使用的JObject具有Descendents()方法,这使实现变得容易。 JToken doesn't have that. JToken没有。 My implementation mutates the JObject itself rather than creating a copy of it.我的实现改变了JObject本身,而不是创建它的副本。 Also, it continues removing properties until there aren't any more occurrences.此外,它会继续删除属性,直到不再出现。 It's a bit more succinct than the other implementations.它比其他实现更简洁。 I don't know how it compares performance-wise.我不知道它如何比较性能。

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

The following is the program output:以下是程序输出:

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": ""
    },
    {}
  ]
}

Using JsonPath we can have a more elegant solution:使用JsonPath我们可以有一个更优雅的解决方案:

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

With a working example here: https://dotnetfiddle.net/zVgXOq这里有一个工作示例: https ://dotnetfiddle.net/zVgXOq

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM