using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public abstract class ReferenceHandlingCustomCreationConverter<T> : JsonConverter where T : class
const string refProperty = "$ref";
const string idProperty = "$id";
public override bool CanConvert(Type objectType)
return typeof(T).IsAssignableFrom(objectType);
protected virtual T Create(Type objectType, T existingValue, JsonSerializer serializer, JObject obj)
return existingValue ?? (T)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
protected abstract void Populate(JObject obj, T value, JsonSerializer serializer);
protected abstract void WriteProperties(JsonWriter writer, T value, JsonSerializer serializer, JsonObjectContract contract);
public override sealed object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
var contract = serializer.ContractResolver.ResolveContract(objectType);
if (!(contract is JsonObjectContract))
throw new JsonSerializationException(string.Format("Invalid non-object contract type {0}", contract));
if (!(existingValue == null || existingValue is T))
throw new JsonSerializationException(string.Format("Converter cannot read JSON with the specified existing value. {0} is required.", typeof(T)));
if (reader.MoveToContent().TokenType == JsonToken.Null)
var obj = JObject.Load(reader);
var refId = (string)obj[refProperty].RemoveFromLowestPossibleParent();
var objId = (string)obj[idProperty].RemoveFromLowestPossibleParent();
var reference = serializer.ReferenceResolver.ResolveReference(serializer, refId);
var value = Create(objectType, (T)existingValue, serializer, obj);
serializer.ReferenceResolver.AddReference(serializer, objId, value);
Populate(obj, value, serializer);
public override sealed void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var contract = serializer.ContractResolver.ResolveContract(value.GetType());
if (!(contract is JsonObjectContract))
throw new JsonSerializationException(string.Format("Invalid non-object contract type {0}", contract));
throw new JsonSerializationException(string.Format("Converter cannot read JSON with the specified existing value. {0} is required.", typeof(T)));
writer.WriteStartObject();
if (serializer.ReferenceResolver.IsReferenced(serializer, value))
writer.WritePropertyName(refProperty);
writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));
writer.WritePropertyName(idProperty);
writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));
WriteProperties(writer, (T)value, serializer, (JsonObjectContract)contract);
public static partial class JsonExtensions
public static JsonReader MoveToContent(this JsonReader reader)
if (reader.TokenType == JsonToken.None)
while (reader.TokenType == JsonToken.Comment && reader.Read())
public static JToken RemoveFromLowestPossibleParent(this JToken node)
var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
if (node.Parent is JProperty)
((JProperty)node.Parent).Value = null;
public class DefaultReferenceHandlingCustomCreationConverter<T> : ReferenceHandlingCustomCreationConverter<T> where T : class
protected override void Populate(JObject obj, T value, JsonSerializer serializer)
using (var reader = obj.CreateReader())
serializer.Populate(reader, value);
protected override void WriteProperties(JsonWriter writer, T value, JsonSerializer serializer, JsonObjectContract contract)
foreach (var property in contract.Properties.Where(p => p.Writable && !p.Ignored))
var itemValue = property.ValueProvider.GetValue(value);
writer.WritePropertyName(property.PropertyName);
serializer.Serialize(writer, itemValue);
public RootObject() { this.Children = new List<RootObject>(); }
public string A { get; set; }
public string B { get; set; }
public List<RootObject> Children { get; set; }
public static void Test()
var parent = new RootObject()
var child = new RootObject
parent.Children.AddRange(new[] { parent, child, child, parent, child });
child.Children.AddRange(new[] { parent, child });
var settings = new JsonSerializerSettings
Converters = { new DefaultReferenceHandlingCustomCreationConverter<RootObject>() },
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
var json = JsonConvert.SerializeObject(parent, Formatting.Indented, settings);
var parent2 = JsonConvert.DeserializeAnonymousType(json, parent, settings);
var json2 = JsonConvert.SerializeObject(parent2, Formatting.Indented, settings);
Assert.IsTrue(json == json2);
Assert.IsTrue(parent.Children.Distinct().Count() == parent2.Children.Distinct().Count());
Assert.IsTrue(parent.Children.Select(c => c == parent).SequenceEqual(parent2.Children.Select(c => c == parent2)));
public static void Main()
Console.WriteLine("Environment version: " + Environment.Version);
Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");