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