PoweredByOrange PoweredByOrange - 6 months ago 134
JSON Question

JSON.NET serialize JObject while ignoring null properties

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!

Answer

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

Comments