13

I have an object that contains several properties that are a List of strings List<String> or a dictionary of strings Dictionary<string,string>. I want to serialize the object to json using Json.net and I want to have the least amount of text generated.

I am using the DefaultValueHandling and NullValueHandling to set default values to strings and integers. But how can I define the DefaultValueHandling to ignore the property in the serialized output if it is initialized to an empty List<String> or Dictionary<string,string>?

Some sample output is:

{
 "Value1": "my value",
 "Value2": 3,
 "List1": [],
 "List2": []
}

I want to get a result that ignores the two lists in the above example, because they are set to the default value of an empty list.

Any help will be appreciated

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
agarcian
  • 3,909
  • 3
  • 33
  • 55
  • I don't think that is possible. I'm sure it wouldn't be too difficult to ignore `null` values but that would require you to make changes elsewhere so that those references are null rather than pointing to an empty list. – evanmcdonnal Aug 27 '13 at 17:36
  • I am sure this can be done with a custom converter (by implementing the abstract class JsonConverter), But I am too lazy for now to provide a code. – I4V Aug 27 '13 at 23:06
  • I found this article that contains a reference to the IContractResolver. http://james.newtonking.com/archive/2009/10/23/efficient-json-with-json-net-reducing-serialized-json-size.aspx That seems to be a good lead to find a way to implement this. – agarcian Aug 28 '13 at 01:05

2 Answers2

19

I have implemented this feature in the custom contract resolver of my personal framework (link to the specific commit in case the file will be moved later). It uses some helper methods and includes some unrelated code for custom references syntax. Without them, the code will be:

public class SkipEmptyContractResolver : DefaultContractResolver
{
    public SkipEmptyContractResolver (bool shareCache = false) : base(shareCache) { }

    protected override JsonProperty CreateProperty (MemberInfo member,
            MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
        bool isDefaultValueIgnored =
            ((property.DefaultValueHandling ?? DefaultValueHandling.Ignore)
                & DefaultValueHandling.Ignore) != 0;
        if (isDefaultValueIgnored
                && !typeof(string).IsAssignableFrom(property.PropertyType)
                && typeof(IEnumerable).IsAssignableFrom(property.PropertyType)) {
            Predicate<object> newShouldSerialize = obj => {
                var collection = property.ValueProvider.GetValue(obj) as ICollection;
                return collection == null || collection.Count != 0;
            };
            Predicate<object> oldShouldSerialize = property.ShouldSerialize;
            property.ShouldSerialize = oldShouldSerialize != null
                ? o => oldShouldSerialize(o) && newShouldSerialize(o)
                : newShouldSerialize;
        }
        return property;
    }
}

This contract resolver will skip serialization of all empty collections (all types implementing ICollection and having Length == 0), unless DefaultValueHandling.Include is specified for the property or the field.

Stef Heyenrath
  • 9,335
  • 12
  • 66
  • 121
Athari
  • 33,702
  • 16
  • 105
  • 146
  • I think you can remove the need for `memberProp` and `memberField` by doing `object value = property.ValueProvider.GetValue(obj);`. – Quartermeister Jun 19 '14 at 14:30
  • I do already use a other contract resolver (CamelCasePropertyNamesContractResolver) can I use both parallel? – Gerwald Feb 12 '16 at 19:35
  • 1
    @Gerwald You'll need to join their code. JSON.NET doesn't support combining contract resolvers. – Athari Feb 12 '16 at 21:15
  • @Discord: ok, thanks a lot. But thats too bad for good reusability. With join, I guess, you mean inheriting? – Gerwald Feb 12 '16 at 22:13
  • @Gerwald Either inheriting or copy-pasting (if inheriting doesn't work). If you need you can create a contract resolver which supports composition, I think. Shouldn't require much code. But you'll definitely have to copy-paste CamelCase resolver's code then. – Athari Feb 13 '16 at 00:27
  • 1
    That's not working as expected, `property.DefaultValueHandling` is always null even if I set it explicitly in the configuration – Tseng Mar 05 '16 at 15:52
10

Another very simple solution is to implement a ShouldSerialize* method in the type being serialized as outline here.

This might be the preferred way if you're in control of the type being serialized and if it is not a general behavior you want to introduce.

Dejan
  • 9,150
  • 8
  • 69
  • 117