CSharp · Console
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;
}
}
}