using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
[JsonObjectContractModifier(typeof(TestContractModifier))]
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
class TestContractModifier : JsonObjectContractModifier
class EmptyValueProvider : IValueProvider
static EmptyValueProvider() { }
internal static readonly EmptyValueProvider Instance = new EmptyValueProvider();
#region IValueProvider Members
public object GetValue(object target)
var test = target as Test;
return test.A == null && test.B == null && test.C == null;
public void SetValue(object target, object value)
var property = target as Test;
if (value != null && value.GetType() == typeof(bool) && (bool)value == true)
property.A = property.B = property.C = null;
public override void ModifyContract(Type objectType, JsonObjectContract contract)
var jsonProperty = new JsonProperty
PropertyName = "isEmpty",
UnderlyingName = "isEmpty",
PropertyType = typeof(bool?),
NullValueHandling = NullValueHandling.Ignore,
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
static SubValueProvider() { }
internal static readonly SubValueProvider Instance = new SubValueProvider();
#region IValueProvider Members
public object GetValue(object target)
var subTest = target as SubTest;
return string.Format("'{0}'-'{1}'-'{2}'-'{3}'", subTest.A, subTest.B, subTest.C, subTest.D);
public void SetValue(object target, object value)
public override void ModifyContract(Type objectType, JsonObjectContract contract)
var jsonProperty = new JsonProperty
PropertyName = "hashValue",
UnderlyingName = "hashValue",
PropertyType = typeof(string),
NullValueHandling = NullValueHandling.Ignore,
DeclaringType = typeof(SubTest),
ValueProvider = SubValueProvider.Instance,
contract.Properties.Add(jsonProperty);
public class ModifierContractResolver : DefaultContractResolver
static ModifierContractResolver instance;
static ModifierContractResolver() { instance = new ModifierContractResolver(); }
public static ModifierContractResolver Instance { get { return instance; } }
protected override JsonObjectContract CreateObjectContract(Type objectType)
var contract = base.CreateObjectContract(objectType);
foreach (var attr in objectType.GetCustomAttributes<JsonObjectContractModifierAttribute>(true).Reverse())
var modifier = (JsonObjectContractModifier)Activator.CreateInstance(attr.ContractModifierType, true);
modifier.ModifyContract(objectType, 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 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);
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.");
public static void Main()