Last active
November 5, 2023 23:08
-
-
Save mikerochip/f38d5606afaeff61eae1f953f244710a to your computer and use it in GitHub Desktop.
Json.NET boilerplate to ignore serializing empty values and "read only" properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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