using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using System.ComponentModel;
using System.Globalization;
using System.Collections.Specialized;
using System.Web.Routing;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Runtime.Serialization.Formatters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public Chacha NextChacha { get; set; }
public long Data { get; set; }
public override string ToString()
return base.ToString() + ": " + Data.ToString();
class ChachaConverter : LinkedListItemConverter<Chacha>
protected override bool IsNextItemProperty(JsonProperty member)
return member.UnderlyingName == "NextChacha";
public abstract class LinkedListItemConverter<T> : JsonConverter where T : class
const string refProperty = "$ref";
const string idProperty = "$id";
const string NextItemListProperty = "nextItemList";
int Level { get { return level; } set { level = value; } }
public override bool CanConvert(Type objectType)
return typeof(T).IsAssignableFrom(objectType);
protected abstract bool IsNextItemProperty(JsonProperty member);
List<T> GetNextItemList(object value, JsonObjectContract contract)
var property = contract.Properties.Where(p => IsNextItemProperty(p)).Single();
for (var item = (T)property.ValueProvider.GetValue(value); item != null; item = (T)property.ValueProvider.GetValue(item))
void SetNextItemLinks(object value, List<T> list, JsonObjectContract contract)
var property = contract.Properties.Where(p => IsNextItemProperty(p)).Single();
if (list == null || list.Count == 0)
foreach (var next in list)
property.ValueProvider.SetValue(previous, next);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
using (new PushValue<int>(Level + 1, () => Level, (old) => Level = old))
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));
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
foreach (var property in contract.Properties
.Where(p => p.Readable && !p.Ignored && (p.ShouldSerialize == null || p.ShouldSerialize(value))))
if (IsNextItemProperty(property))
var propertyValue = property.ValueProvider.GetValue(value);
if (propertyValue == null && serializer.NullValueHandling == NullValueHandling.Ignore)
writer.WritePropertyName(property.PropertyName);
serializer.Serialize(writer, propertyValue);
var nextItems = GetNextItemList(value, contract);
writer.WritePropertyName(NextItemListProperty);
serializer.Serialize(writer, nextItems);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
if (reader.TokenType == JsonToken.Null)
var jObject = JObject.Load(reader);
var refValue = (string)jObject[refProperty].RemoveFromLowestPossibleParent();
var reference = serializer.ReferenceResolver.ResolveReference(serializer, refValue);
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(existingValue == null ? typeof(T) : existingValue.GetType());
T value = (existingValue as T ?? (T)contract.DefaultCreator());
var idValue = (string)jObject[idProperty].RemoveFromLowestPossibleParent();
serializer.ReferenceResolver.AddReference(serializer, idValue, value);
var nextItemList = jObject[NextItemListProperty].RemoveFromLowestPossibleParent();
serializer.Populate(jObject.CreateReader(), value);
if (nextItemList != null)
var list = nextItemList.ToObject<List<T>>(serializer);
SetNextItemLinks(value, list, contract);
public struct PushValue<T> : IDisposable
public PushValue(T value, Func<T> getValue, Action<T> setValue)
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
#region IDisposable Members
public static class JsonExtensions
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 static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
TypeNameHandling = TypeNameHandling.All,
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
Converters = { new ChachaConverter() },
TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Full
public static void Test()
Test(3, Formatting.Indented);
Test(count, Formatting.None);
private static void Test(int count, Formatting formatting)
Console.WriteLine("Testing an array of {0} items of type {1}", count, typeof(Chacha));
Chacha[] steps = new Chacha[count];
steps[0] = new Chacha { Data = 0 };
for (int i = 1; i < count; i++)
steps[i] = new Chacha { Data = i };
steps[i - 1].NextChacha = steps[i];
AssertValidSteps(steps, count);
string serSteps = JsonConvert.SerializeObject(steps, formatting, Settings);
Console.WriteLine("Beginning of serialized JSON: ");
Console.WriteLine(serSteps.Substring(0, Math.Min(2000, serSteps.Length)));
var newSteps = JsonConvert.DeserializeObject<Chacha[]>(serSteps, Settings);
AssertValidSteps(newSteps, count);
Console.WriteLine("Json deserialized successfully.");
static void AssertValidSteps(Chacha[] steps, int count)
Assert.That(steps.Length == count);
Assert.That(steps[0].Data == 0);
for (int i = 1; i < count; i++)
Assert.That(steps[i].Data == i);
Assert.That(steps[i - 1].NextChacha == steps[i]);
public class AssertionFailedException : System.Exception
public AssertionFailedException() : base() { }
public AssertionFailedException(string s) : base(s) { }
public static class Assert
public static void That(bool value)
throw new AssertionFailedException("failed");
public static void Main()
Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);