JSON.NET serialize JObject while ignoring null properties

后端 未结 4 1866
说谎
说谎 2020-12-01 16:37

I have a JObject which is used as a template for calling RESTful web services. This JObject gets created via a parser and since it

相关标签:
4条回答
  • 2020-12-01 16:56

    You can use a recursive helper method like the one below to remove the null values from your JToken hierarchy prior to serializing it.

    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

    Notice that, after removing all null values, you will have an empty object in the FooArray, which you may not want. (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:

        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

    0 讨论(0)
  • 2020-12-01 16:59

    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();
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-01 17:08

    You can prevent the null tokens from being created to begin with by specifying the JsonSerializer with its NullValueHandler set to 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.

    0 讨论(0)
  • 2020-12-01 17:14

    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. 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. JToken doesn't have that. My implementation mutates the JObject itself rather than creating a copy of it. 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": ""
        },
        {}
      ]
    }
    
    0 讨论(0)
提交回复
热议问题