JSON.NET serialize JObject while ignoring null properties

纵然是瞬间 提交于 2019-11-26 14:22:30

问题


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'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. 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":{  
      "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. 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. 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; 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!


回答1:


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




回答2:


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



回答3:


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.



来源:https://stackoverflow.com/questions/33027409/json-net-serialize-jobject-while-ignoring-null-properties

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!