using System.Reflection; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; namespace DysonNetwork.Shared.Data; public static class JsonExtensions { public static Action UnignoreAllProperties() => typeInfo => { if (typeInfo.Kind == JsonTypeInfoKind.Object) // [JsonIgnore] is implemented by setting ShouldSerialize to a function that returns false. foreach (var property in typeInfo.Properties.Where(ShouldUnignore)) { property.Get ??= CreatePropertyGetter(property); property.Set ??= CreatePropertySetter(property); if (property.Get != null) property.ShouldSerialize = null; } }; public static Action UnignoreAllProperties(Type type) => typeInfo => { if (type.IsAssignableFrom(typeInfo.Type) && typeInfo.Kind == JsonTypeInfoKind.Object) // [JsonIgnore] is implemented by setting ShouldSerialize to a function that returns false. foreach (var property in typeInfo.Properties.Where(ShouldUnignore)) { property.Get ??= CreatePropertyGetter(property); property.Set ??= CreatePropertySetter(property); if (property.Get != null) property.ShouldSerialize = null; } }; public static Action UnignoreProperties(Type type, params string[] properties) => typeInfo => { if (type.IsAssignableFrom(typeInfo.Type) && typeInfo.Kind == JsonTypeInfoKind.Object) // [JsonIgnore] is implemented by setting ShouldSerialize to a function that returns false. foreach (var property in typeInfo.Properties.Where(p => ShouldUnignore(p, properties))) { property.Get ??= CreatePropertyGetter(property); property.Set ??= CreatePropertySetter(property); if (property.Get != null) property.ShouldSerialize = null; } }; public static Action UnignorePropertiesForDeserialize(Type type, params string[] properties) => typeInfo => { if (type.IsAssignableFrom(typeInfo.Type) && typeInfo.Kind == JsonTypeInfoKind.Object) // [JsonIgnore] is implemented by setting ShouldSerialize to a function that returns false. foreach (var property in typeInfo.Properties.Where(p => ShouldUnignore(p, properties))) { property.Set ??= CreatePropertySetter(property); } }; static bool ShouldUnignore(JsonPropertyInfo property) => property.ShouldSerialize != null && property.AttributeProvider?.IsDefined(typeof(JsonIgnoreAttribute), true) == true; static bool ShouldUnignore(JsonPropertyInfo property, string[] properties) => property.ShouldSerialize != null && property.AttributeProvider?.IsDefined(typeof(JsonIgnoreAttribute), true) == true && properties.Contains(property.GetMemberName()); // CreateGetter() and CreateSetter() taken from this answer https://stackoverflow.com/a/76296944/3744182 // To https://stackoverflow.com/questions/61869393/get-net-core-jsonserializer-to-serialize-private-members delegate TValue RefFunc(ref TObject arg); static Func? CreatePropertyGetter(JsonPropertyInfo property) => property.GetPropertyInfo() is { } info && info.ReflectedType != null && info.GetGetMethod() is { } getMethod ? CreateGetter(info.ReflectedType, getMethod) : null; static Func? CreateGetter(Type type, MethodInfo? method) { if (method == null) return null; var myMethod = typeof(JsonExtensions).GetMethod(nameof(JsonExtensions.CreateGetterGeneric), BindingFlags.NonPublic | BindingFlags.Static)!; return (Func)(myMethod.MakeGenericMethod(new[] { type, method.ReturnType }) .Invoke(null, new[] { method })!); } static Func CreateGetterGeneric(MethodInfo method) { if (method == null) throw new ArgumentNullException(); if (typeof(TObject).IsValueType) { // https://stackoverflow.com/questions/4326736/how-can-i-create-an-open-delegate-from-a-structs-instance-method // https://stackoverflow.com/questions/1212346/uncurrying-an-instance-method-in-net/1212396#1212396 var func = (RefFunc)Delegate.CreateDelegate(typeof(RefFunc), null, method); return (o) => { var tObj = (TObject)o; return func(ref tObj); }; } else { var func = (Func)Delegate.CreateDelegate(typeof(Func), method); return (o) => func((TObject)o); } } static Action? CreatePropertySetter(JsonPropertyInfo property) => property.GetPropertyInfo() is { } info && info.ReflectedType != null && info.GetSetMethod() is { } setMethod ? CreateSetter(info.ReflectedType, setMethod) : null; static Action? CreateSetter(Type type, MethodInfo? method) { if (method == null) return null; var myMethod = typeof(JsonExtensions).GetMethod(nameof(JsonExtensions.CreateSetterGeneric), BindingFlags.NonPublic | BindingFlags.Static)!; return (Action)(myMethod .MakeGenericMethod(new[] { type, method.GetParameters().Single().ParameterType }) .Invoke(null, new[] { method })!); } static Action? CreateSetterGeneric(MethodInfo method) { if (method == null) throw new ArgumentNullException(); if (typeof(TObject).IsValueType) { // TODO: find a performant way to do this. Possibilities: // Box from Microsoft.Toolkit.HighPerformance // https://stackoverflow.com/questions/18937935/how-to-mutate-a-boxed-struct-using-il return (o, v) => method.Invoke(o, new[] { v }); } else { var func = (Action)Delegate.CreateDelegate(typeof(Action), method); return (o, v) => func((TObject)o, (TValue?)v); } } static PropertyInfo? GetPropertyInfo(this JsonPropertyInfo property) => (property.AttributeProvider as PropertyInfo); static string? GetMemberName(this JsonPropertyInfo property) => (property.AttributeProvider as MemberInfo)?.Name; }