[Fork] questions/52518593/how-to-force-jsonconverter-writejson-to-be-called-for-a-null-value

CSharp · Console

/Program.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Formatters;
using System.IO;
using System.Data;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Reflection;

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

using NUnit.Framework;

    //https://stackoverflow.com/questions/52518593/how-to-force-jsonconverter-writejson-to-be-called-for-a-null-value

    public class CustomContractResolver : DefaultContractResolver
    {
        // Because contracts are cached, WrappedTypes must not be modified after construction.
        readonly HashSet<Type> WrappedTypes = new HashSet<Type>();

        public CustomContractResolver(IEnumerable<Type> wrappedTypes)
        {
            if (wrappedTypes == null)
                throw new ArgumentNullException();
            foreach (var type in wrappedTypes)
                WrappedTypes.Add(type);
        }

        class VersionWrapperProvider<T> : IValueProvider
        {
            readonly IValueProvider baseProvider;

            public VersionWrapperProvider(IValueProvider baseProvider)
            {
                if (baseProvider == null)
                    throw new ArgumentNullException();
                this.baseProvider = baseProvider;
            }

            public object GetValue(object target)
            {
                return new VersionWrapper<T>(target, baseProvider);
            }

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

        class ReadOnlyVersionWrapperProvider<T> : IValueProvider
        {
            readonly IValueProvider baseProvider;

            public ReadOnlyVersionWrapperProvider(IValueProvider baseProvider)
            {
                if (baseProvider == null)
                    throw new ArgumentNullException();
                this.baseProvider = baseProvider;
            }

            public object GetValue(object target)
            {
                return new ReadOnlyVersionWrapper<T>(target, baseProvider);
            }

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

        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            JsonProperty property = base.CreateProperty(member, memberSerialization);
            if (WrappedTypes.Contains(property.PropertyType) 
                && !(member.DeclaringType.IsGenericType 
                    && (member.DeclaringType.GetGenericTypeDefinition() == typeof(VersionWrapper<>) || member.DeclaringType.GetGenericTypeDefinition() == typeof(ReadOnlyVersionWrapper<>))))
            {
                var wrapperGenericType = (property.Writable ? typeof(VersionWrapper<>) : typeof(ReadOnlyVersionWrapper<>));
                var providerGenericType = (property.Writable ? typeof(VersionWrapperProvider<>) : typeof(ReadOnlyVersionWrapperProvider<>));
                var wrapperType = wrapperGenericType.MakeGenericType(new[] { property.PropertyType });
                var providerType = providerGenericType.MakeGenericType(new[] { property.PropertyType });
                property.PropertyType = wrapperType;
                property.ValueProvider = (IValueProvider)Activator.CreateInstance(providerType, property.ValueProvider);
                property.ObjectCreationHandling = ObjectCreationHandling.Reuse;
            }

            return property;
        }
    }

    internal class VersionWrapper<T>
    {
        readonly object target;
        readonly IValueProvider baseProvider;

        public VersionWrapper(object target, IValueProvider baseProvider)
        {
            this.target = target;
            this.baseProvider = baseProvider;
        }

        public int Version { get { return 1; } }

        [JsonProperty(NullValueHandling = NullValueHandling.Include)]
        public T Object 
        {
            get
            {
                return (T)baseProvider.GetValue(target);
            }
            set
            {
                baseProvider.SetValue(target, value);
            }
        }
    }

    internal class ReadOnlyVersionWrapper<T>
    {
        readonly object target;
        readonly IValueProvider baseProvider;

        public ReadOnlyVersionWrapper(object target, IValueProvider baseProvider)
        {
            this.target = target;
            this.baseProvider = baseProvider;
        }

        public int Version { get { return 1; } }

        [JsonProperty(NullValueHandling = NullValueHandling.Include)]
        public T Object
        {
            get
            {
                return (T)baseProvider.GetValue(target);
            }
        }
    }
	
    public class MyContent
    {
        public string Content { get; set; }
    }

    public class Wrap
    {
        public MyContent Prop { get; set; }

        public MyContent GetOnlyProperty { get { return Prop; } }
    }

    class TestClass
    {
        static IContractResolver resolver = new CustomContractResolver(new[] { typeof(MyContent) });

        public static void Test()
        {
            foreach (var content in Contents())
            {
                var wrap = new Wrap { Prop = content };
                var settings = new JsonSerializerSettings
                {
                    ContractResolver = resolver,
                };
                var json = JsonConvert.SerializeObject(wrap, Formatting.Indented, settings);

                Console.WriteLine("\nSimple JSON: ");
                Console.WriteLine(JsonConvert.SerializeObject(wrap));
                Console.WriteLine("Wrapped JSON: ");
                Console.WriteLine(json);

                var wrap2 = JsonConvert.DeserializeObject<Wrap>(json, settings);

                Assert.IsTrue(wrap.Prop == wrap2.Prop || wrap.Prop.Content == wrap2.Prop.Content);
            }

            TestPopulate();

            Console.WriteLine("\nAll tests passed.");
        }

        static void TestPopulate()
        {
            Console.WriteLine("\nTesting populate...");

            var wrap1 = new Wrap
            {
                Prop = new MyContent { Content = "abc" },
            };

            var wrap2 = new Wrap
            {
                Prop = new MyContent { Content = "def" },
            };

            var settings = new JsonSerializerSettings
            {
                ContractResolver = resolver,
            };

            var json = JsonConvert.SerializeObject(wrap1, settings);

            JsonConvert.PopulateObject(json, wrap2, settings);

            Assert.IsTrue(wrap2.Prop.Content == wrap1.Prop.Content);
        }

        static IEnumerable<MyContent> Contents()
        {
            return new MyContent []
            {
                new MyContent { Content = "abc" },
                new MyContent(),
                null,
            };
        }
    }

	public class Program
	{
		public static void Main()
		{
			Console.WriteLine("Environment version: " + Environment.Version);
			Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);
			Console.WriteLine();

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