How to ignore empty object literals in the produced JSON?

In the comments it looks like you have decided to resort to using Regex to get rid of the empty objects. One problem with that idea is it probably will not handle the situation where you have what I will call “recursive empty objects”. In other words something like this:

{
    "foo":
    {
        "bar": {},
        "baz": {}
    }
}

If you manage to remove the deepest level empty objects bar and baz with Regex (while also realizing that you need to remove the comma between them to keep the JSON valid), you will still have an empty object left: foo.

{
    "foo":
    {
    }
}

I think a better solution is to load your data into a JToken hierarchy and then use a recursive method to remove all the empty children before writing it out to JSON. Something like this should work for your needs:

using System;
using Newtonsoft.Json.Linq;

public static class JsonHelper
{
    public static string SerializeToMinimalJson(object obj)
    {
        return JToken.FromObject(obj).RemoveEmptyChildren().ToString();
    }

    public static JToken RemoveEmptyChildren(this 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 = child.RemoveEmptyChildren();
                }
                if (!child.IsEmptyOrDefault())
                {
                    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 = child.RemoveEmptyChildren();
                }
                if (!child.IsEmptyOrDefault())
                {
                    copy.Add(child);
                }
            }
            return copy;
        }
        return token;
    }

    public static bool IsEmptyOrDefault(this JToken token)
    {
        return (token.Type == JTokenType.Array && !token.HasValues) ||
               (token.Type == JTokenType.Object && !token.HasValues) ||
               (token.Type == JTokenType.String && token.ToString() == String.Empty) ||
               (token.Type == JTokenType.Boolean && token.Value<bool>() == false) ||
               (token.Type == JTokenType.Integer && token.Value<int>() == 0) ||
               (token.Type == JTokenType.Float && token.Value<double>() == 0.0) || 
               (token.Type == JTokenType.Null);
    }

}

You can then serialize your object(s) like this:

var json = JsonHelper.SerializeToMinimalJson(obj);

Fiddle: https://dotnetfiddle.net/awRPMR

EDIT

If you want to honor the [DefaultValue] attribute with this method, you can do so by modifying the SerializeToMinimalJson() method to create an instance of the JsonSerializer, setting the DefaultValueHandling property on it, and then passing it to JToken.FromObject() as shown below. (It has to be done this way because JTokens do not have references back to the original objects from which they were created using FromObject(), so there’s no way to get the values of the [DefaultValue] attributes after that.)

public static string SerializeToMinimalJson(object obj)
{
    var serializer = new JsonSerializer();
    serializer.NullValueHandling = NullValueHandling.Ignore;
    serializer.DefaultValueHandling = DefaultValueHandling.Ignore;
    return JToken.FromObject(obj, serializer).RemoveEmptyChildren().ToString();
}

If you do that, you may also want to change the IsEmptyOrDefault() method so that it does not remove values that are the “default default”. You can reduce it to this:

public static bool IsEmptyOrDefault(this JToken token)
{
    return (token.Type == JTokenType.Array && !token.HasValues) ||
           (token.Type == JTokenType.Object && !token.HasValues);
}

Fiddle: https://dotnetfiddle.net/0yVRI5

Leave a Comment