Skip to content

Instantly share code, notes, and snippets.

@mikerochip
Last active November 5, 2023 23:08
Show Gist options
  • Save mikerochip/f38d5606afaeff61eae1f953f244710a to your computer and use it in GitHub Desktop.
Save mikerochip/f38d5606afaeff61eae1f953f244710a to your computer and use it in GitHub Desktop.
Json.NET boilerplate to ignore serializing empty values and "read only" properties
public class LeanContractResolver : DefaultContractResolver
{
public static LeanContractResolver Instance { get; } = new();
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.PropertyType == null)
return property;
if (property.UnderlyingName == null)
return property;
if (property.DeclaringType == null)
return property;
var propertyInfo = property.DeclaringType!.GetProperty(property.UnderlyingName!);
if (IsReadOnlyProperty(propertyInfo) || IsExpressionBodiedProperty(property, propertyInfo))
{
property.ShouldSerialize = _ => false;
property.ShouldDeserialize = _ => false;
}
else if (property.PropertyType == typeof(string))
{
property.ShouldSerialize = instance =>
{
var value = (string)GetValue(instance, property, member);
return !string.IsNullOrEmpty(value);
};
}
else if (property.PropertyType == typeof(DateTime))
{
property.ShouldSerialize = instance =>
{
var value = (DateTime)GetValue(instance, property, member);
return value != default;
};
}
else if (typeof(ICollection).IsAssignableFrom(property.PropertyType))
{
property.ShouldSerialize = instance =>
{
var collection = (ICollection)GetValue(instance, property, member);
return collection?.Count > 0;
};
}
return property;
}
private static object GetValue(object instance, JsonProperty property, MemberInfo member)
{
var type = property.DeclaringType!;
var name = property.UnderlyingName!;
return member.MemberType switch
{
MemberTypes.Property => type.GetProperty(name)!.GetValue(instance),
MemberTypes.Field => type.GetField(name)!.GetValue(instance),
_ => null
};
}
private static bool IsReadOnlyProperty(PropertyInfo propertyInfo)
{
if (propertyInfo == null)
return false;
return propertyInfo.SetMethod == null || !propertyInfo.SetMethod.IsPublic;
}
// An expression-bodied property is like this:
// public Foo => _bar;
private static bool IsExpressionBodiedProperty(JsonProperty property, PropertyInfo propertyInfo)
{
if (propertyInfo == null)
return false;
// There is no built-in reflection method to detect whether a property is expression-
// bodied other than to check for the absence of a backing field.
var backingField = property.DeclaringType!.GetField(
$"<{property.PropertyName}>k__BackingField",
BindingFlags.NonPublic | BindingFlags.Instance);
return backingField == null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment