3

tl;dr; In Newtonsoft JSON.NET, how do you ignore default values for some types (enums), and not others (ints)?


My team is consuming a library that uses protocol buffers for their business entities. Every enumeration in this library/protobuf has a default value of 0, "ValueNotSet". My team is using Newtonsoft JSON.NET to serialize these entities. Here's a diluted example for a bakery's inventory:

public enum Flavor { ValueNotSet, Cherry, Blueberry, Cheese };
public class DanishInventory { public int QtyInStock; public Flavor; }

In order to conserve resources we do not want to serialize ValueNotSet (the real world scenario has many enumerations), but having zero cheese danishes in stock is valid and we do want to serialize zero. Because of this, we cannot use DefaultValueHandling = Ignore in settings.

I created a custom JsonConverter, but by the time WriteJson(...) is called the key is already in the JsonWriter. So if I write nothing the JSON is invalid, and I don't see an obvious method to back-track the writer to overwrite a key. So what is the best way to ignore default values for some types (e.g. enums), but not others (e.g. ints)?

Note that the enums are in a NuGet package and cannot be modified, e.g. by adding attributes.

dbc
  • 104,963
  • 20
  • 228
  • 340
Steve H.
  • 3,283
  • 4
  • 24
  • 32

2 Answers2

2

You can use a variation of the DefaultValueContractResolver from this answer to Json.NET: How to make DefaultValueHandling only apply to certain types? to exclude all enum-valued properties with default values:

public class EnumDefaultValueContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        if (property.DefaultValueHandling == null)
        {
            if (property.PropertyType.IsEnum)
            {
                //For safety you could check here if the default value is named ValueNotSet and only set IgnoreAndPopulate in that case.
                //var defaultValue = Enum.ToObject(property.PropertyType, 0);
                //if (defaultValue.ToString() == "ValueNotSet")
                //{                 
                    property.DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate; // Or DefaultValueHandling.Ignore if you prefer
                //}
            }
        }

        return property;
    }
}

Then use it as follows:

var resolver = new EnumDefaultValueContractResolver();

var settings = new JsonSerializerSettings { ContractResolver = resolver };
var json = JsonConvert.SerializeObject(inventory, settings);

You may want to cache the contract resolver for best performance.

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
1

Many serializers, (including Json.NET I believe) support the ShouldSerialize*() pattern; if you don't mind doing this on a per-usage basis, you could do:

public class DanishInventory {
    public int QtyInStock;
    public Flavor;
    public bool ShouldSerializeFlavor() => Flavor != 0;
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900