How to omit/ignore/skip empty object literals in the produced JSON?
I'm using Json.NET
to convert a complex C#
object graph to JSON. Due to ignoring properties which have default values in the object, I usually get empty object literals in the output, which I'd like to omit.
For example:
public class Sample {
public int Value { get; set; }
public string Name { get; set; }
}
public class ParentSample {
// this property should never be null, hence the initializer
public Sample Sample { get; } = new Sample();
}
..
var obj = new ParentSample();
// settings for indentation and excluding default values omitted for clarity
var output = JsonConvert.SerializeObject(obj, ... );
// output will be
// {
// Sample: {}
// }
//
// I'd like it to be
// {}
I'm aware of some type specific solutions like adding a ShouldSerializeSample
boolean method to the ParentSample
type and check if all properties are default there. However I'd like a general solution in the form of a custom contract resolver for example.
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