https://stackoverflow.com/questions/76866758/system-text-json-ignore-jsonignore-attribute-when-de-serializing

CSharp · Console

/Program.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Reflection;

using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.Json.Nodes;

using NUnit.Framework; // Throws NUnit.Framework.AssertionException

//https://stackoverflow.com/questions/76866758/system-text-json-ignore-jsonignore-attribute-when-de-serializing

#nullable enable

	public static partial class JsonExtensions
	{
		public static Action<JsonTypeInfo> 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<JsonTypeInfo> 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, 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<TObject, TValue>(ref TObject arg);

		static Func<object, object?>? CreatePropertyGetter(JsonPropertyInfo property) =>
			property.GetPropertyInfo() is {} info && info.ReflectedType != null && info.GetGetMethod() is {} getMethod
			? CreateGetter(info.ReflectedType, getMethod) : null;
		
		static Func<object, object?>? CreateGetter(Type type, MethodInfo? method)
		{
			if (method == null)
				return null;
			var myMethod = typeof(JsonExtensions).GetMethod(nameof(JsonExtensions.CreateGetterGeneric), BindingFlags.NonPublic | BindingFlags.Static)!;
			return (Func<object, object?>)(myMethod.MakeGenericMethod(new[] { type, method.ReturnType }).Invoke(null, new[] { method })!);
		}

		static Func<object, object?> CreateGetterGeneric<TObject, TValue>(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<TObject, TValue>)Delegate.CreateDelegate(typeof(RefFunc<TObject, TValue>), null, method);
				return (o) => {var tObj = (TObject)o; return func(ref tObj); };
			}
			else
			{
				var func = (Func<TObject, TValue>)Delegate.CreateDelegate(typeof(Func<TObject, TValue>), method);
				return (o) => func((TObject)o);
			}
		}

		static Action<object,object?>? CreatePropertySetter(JsonPropertyInfo property) =>
			property.GetPropertyInfo() is {} info && info.ReflectedType != null && info.GetSetMethod() is {} setMethod
			? CreateSetter(info.ReflectedType, setMethod) : null;

		static Action<object,object?>? CreateSetter(Type type, MethodInfo? method)
		{
			if (method == null)
				return null;
			var myMethod = typeof(JsonExtensions).GetMethod(nameof(JsonExtensions.CreateSetterGeneric), BindingFlags.NonPublic | BindingFlags.Static)!;
			return (Action<object,object?>)(myMethod.MakeGenericMethod(new [] { type, method.GetParameters().Single().ParameterType }).Invoke(null, new[] { method })!);
		}

		static Action<object,object?>? CreateSetterGeneric<TObject, TValue>(MethodInfo method)
		{
			if (method == null)
				throw new ArgumentNullException();
			if (typeof(TObject).IsValueType)
			{
				// TODO: find a performant way to do this.  Possibilities:
				// Box<T> 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<TObject, TValue?>)Delegate.CreateDelegate(typeof(Action<TObject, TValue?>), 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;
	}

	public class MyClass
	{
		[JsonIgnore]
		public string IgnoreMe { get; set; } = "";
		[JsonIgnore]
		public int AlsoIgnoreMe { get; set; }
	}

	public class ReadOnlyTest
	{
		[JsonIgnore]
		public string IgnoreMe { get; } = "ignored read only";
	}

    class TestClass
    {
        public static void Test()
        {
			TestRoundTrip();
			TestDeserializeOnly();
			TestReadOnly();
		}	
		
		static void TestRoundTrip()
		{
			var myClass = new MyClass { IgnoreMe = "I Should Appear", AlsoIgnoreMe = 101 };

			var options = new JsonSerializerOptions
			{
				TypeInfoResolver = new DefaultJsonTypeInfoResolver
				{
					Modifiers = { JsonExtensions.UnignoreProperties(typeof(MyClass), 
																	nameof(MyClass.IgnoreMe), nameof(MyClass.AlsoIgnoreMe)) },
				},
			};

			var json = JsonSerializer.Serialize(myClass, options);
			
			var myClass2 = JsonSerializer.Deserialize<MyClass>(json, options);
			
			Console.WriteLine(json);
			
			Assert.AreEqual("{\"IgnoreMe\":\"I Should Appear\",\"AlsoIgnoreMe\":101}", json);
			Assert.AreEqual(myClass.IgnoreMe, myClass2!.IgnoreMe);
			Assert.AreEqual(myClass.AlsoIgnoreMe, myClass2!.AlsoIgnoreMe);

			var json2 = JsonSerializer.Serialize(myClass2, options);
			Assert.AreEqual(json, json2);
		}
		
		static void TestDeserializeOnly()
		{
			var json = 
				"""
				{"IgnoreMe":"I Should Appear","AlsoIgnoreMe":101}
				""";
			
			var options = new JsonSerializerOptions
			{
				TypeInfoResolver = new DefaultJsonTypeInfoResolver
				{
					Modifiers = { JsonExtensions.UnignorePropertiesForDeserialize(typeof(MyClass), 
																				  nameof(MyClass.IgnoreMe), nameof(MyClass.AlsoIgnoreMe)) },
				},
			};

			var myClass2 = JsonSerializer.Deserialize<MyClass>(json, options);
			
			Assert.AreEqual(myClass2!.IgnoreMe, "I Should Appear");
			Assert.AreEqual(myClass2!.AlsoIgnoreMe, 101);

			var json2 = JsonSerializer.Serialize(myClass2, options);
			Assert.AreEqual("{}", json2);
		}
		
		static void TestReadOnly()
		{
			var test = new ReadOnlyTest();
			
			var options = new JsonSerializerOptions
			{
				TypeInfoResolver = new DefaultJsonTypeInfoResolver
				{
					Modifiers = { JsonExtensions.UnignoreProperties(typeof(ReadOnlyTest), nameof(ReadOnlyTest.IgnoreMe)) },
				},
			};

			var json = JsonSerializer.Serialize(test, options);

			Assert.AreEqual("{\"IgnoreMe\":\"ignored read only\"}", json);

			Console.WriteLine(json);
		}
	}

	public class Program
	{
		public static void Main()
		{
			Console.WriteLine("Environment version: {0} ({1}), {2}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version, Environment.OSVersion);
			Console.WriteLine("System.Text.Json version: " + typeof(JsonSerializer).Assembly.FullName);
			Console.WriteLine();

			try
			{
				TestClass.Test();
			}
			catch (Exception ex)
			{
				Console.WriteLine("Failed with unhandled exception: ");
				Console.WriteLine(ex);
				throw;
			}
		}
	}
;