Question37761625 modify default serialization

CSharp · Console

/Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;


    [JsonObjectContractModifier(typeof(TestContractModifier))]
    public class Test
    {
        public string A { get; set; }
        public string B { get; set; }
        public string C { get; set; }
    }

    class TestContractModifier : JsonObjectContractModifier
    {
        class EmptyValueProvider : IValueProvider
        {
            // Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
            static EmptyValueProvider() { }

            internal static readonly EmptyValueProvider Instance = new EmptyValueProvider();

            #region IValueProvider Members

            public object GetValue(object target)
            {
                var test = target as Test;
                if (test == null)
                    return null;
                return test.A == null && test.B == null && test.C == null;
            }

            public void SetValue(object target, object value)
            {
                var property = target as Test;
                if (property == null)
                    return;
                if (value != null && value.GetType() == typeof(bool) && (bool)value == true)
                {
                    property.A = property.B = property.C = null;
                }
            }

            #endregion
        }

        public override void ModifyContract(Type objectType, JsonObjectContract contract)
        {
            var jsonProperty = new JsonProperty
            {
                PropertyName = "isEmpty",
                UnderlyingName = "isEmpty",
                PropertyType = typeof(bool?),
                NullValueHandling = NullValueHandling.Ignore,
                Readable = true,
                Writable = true,
                DeclaringType = typeof(Test),
                ValueProvider = EmptyValueProvider.Instance,
            };

            contract.Properties.Add(jsonProperty);
        }
    }

    [JsonObjectContractModifier(typeof(SubTestContractModifier))]
    public class SubTest : Test
    {
        public string D { get; set; }
    }

    class SubTestContractModifier : JsonObjectContractModifier
    {
        class SubValueProvider : IValueProvider
        {
            // Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
            static SubValueProvider() { }

            internal static readonly SubValueProvider Instance = new SubValueProvider();

            #region IValueProvider Members

            public object GetValue(object target)
            {
                var subTest = target as SubTest;
                if (subTest == null)
                    return null;
                return string.Format("'{0}'-'{1}'-'{2}'-'{3}'", subTest.A, subTest.B, subTest.C, subTest.D);
            }

            public void SetValue(object target, object value)
            {
            }

            #endregion
        }

        public override void ModifyContract(Type objectType, JsonObjectContract contract)
        {
            var jsonProperty = new JsonProperty
            {
                PropertyName = "hashValue",
                UnderlyingName = "hashValue",
                PropertyType = typeof(string),
                NullValueHandling = NullValueHandling.Ignore,
                Readable = true,
                Writable = false,
                DeclaringType = typeof(SubTest),
                ValueProvider = SubValueProvider.Instance,
            };

            contract.Properties.Add(jsonProperty);
        }
    }

    public class ModifierContractResolver : DefaultContractResolver
    {
        // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
        // http://www.newtonsoft.com/json/help/html/ContractResolver.htm
        // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
        // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
        // See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
        static ModifierContractResolver instance;

        // Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
        static ModifierContractResolver() { instance = new ModifierContractResolver(); }

        public static ModifierContractResolver Instance { get { return instance; } }

        protected override JsonObjectContract CreateObjectContract(Type objectType)
        {
            var contract = base.CreateObjectContract(objectType);

            // Apply in reverse order so inherited types are applied after base types.
            foreach (var attr in objectType.GetCustomAttributes<JsonObjectContractModifierAttribute>(true).Reverse())
            {
                var modifier = (JsonObjectContractModifier)Activator.CreateInstance(attr.ContractModifierType, true);
                modifier.ModifyContract(objectType, contract);
            }
            return contract;
        }
    }

    public abstract class JsonObjectContractModifier
    {
        public abstract void ModifyContract(Type objectType, JsonObjectContract contract);
    }

    [System.AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
    public class JsonObjectContractModifierAttribute : System.Attribute
    {
        private readonly Type _contractModifierType;

        public Type ContractModifierType { get { return _contractModifierType; } }

        public JsonObjectContractModifierAttribute(Type contractModifierType)
        {
            if (contractModifierType == null)
            {
                throw new ArgumentNullException("contractModifierType");
            }
            if (!typeof(JsonObjectContractModifier).IsAssignableFrom(contractModifierType))
            {
                throw new ArgumentNullException(string.Format("{0} is not a subtype of {1}", contractModifierType, typeof(JsonObjectContractModifier)));
            }
            this._contractModifierType = contractModifierType;
        }    
    }

    public class TestClass
    {
        public static void Test()
        {
            var property1 = new Test { A = "hello", B = "goodbye", C = "sea" };
            var property2 = new Test();
            var property3 = new SubTest { A = "A Sub", B = "B Sub", C = "C Sub", D = "D Sub" };

            var test = new List<Test> { property1, property1, property2, property1, property2, property1, property3, property3 };

            Test(test, PreserveReferencesHandling.Objects, test.Distinct().Count());
            Test(test, PreserveReferencesHandling.None, test.Count);
            Test(test, PreserveReferencesHandling.Arrays, test.Count);
            Test(test, PreserveReferencesHandling.All, test.Distinct().Count());
        }

        private static void Test(List<Test> root, PreserveReferencesHandling preserveReferencesHandling, int expectedCount)
        {
			Console.WriteLine(string.Format("Testing with: {0}", preserveReferencesHandling));
            var settings = new JsonSerializerSettings 
            { 
                PreserveReferencesHandling = preserveReferencesHandling, 
                ContractResolver = ModifierContractResolver.Instance, 
                TypeNameHandling = TypeNameHandling.Auto,
                TypeNameAssemblyFormat = FormatterAssemblyStyle.Full,
            };
            var json = JsonConvert.SerializeObject(root, Formatting.Indented, settings);
            Console.WriteLine(json);
            var root2 = JsonConvert.DeserializeObject<List<Test>>(json, settings);
            var json2 = JsonConvert.SerializeObject(root2, Formatting.Indented, settings);

            if (root2.Distinct().Count() != expectedCount)
            {
                throw new InvalidOperationException("test.Length != expectedCount");
            }
            if (!JToken.DeepEquals(JToken.Parse(json), JToken.Parse(json2)))
            {
                throw new InvalidOperationException("!JToken.DeepEquals(JToken.Parse(json), JToken.Parse(json2))");
            }
            Console.WriteLine("Test object was deserialized successfully with the expected number of unique objects.");
            Console.WriteLine("");
        }
    }
					
	public class Program
	{
		public static void Main()
		{
			TestClass.Test();
		}
	}
;