using System.Collections.Generic;
using System.Diagnostics;
using System.Collections;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System.Globalization;
public class TypeNameHandlingExpandoObjectConverter : JsonConverter
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
return ReadValue(reader, serializer);
private object ReadValue(JsonReader reader, JsonSerializer serializer)
if (!reader.MoveToContent())
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject.");
switch (reader.TokenType)
case JsonToken.StartObject:
return ReadObject(reader, serializer);
case JsonToken.StartArray:
return ReadList(reader, serializer);
if (JsonTokenUtils.IsPrimitiveToken(reader.TokenType))
throw JsonSerializationExceptionHelper.Create(reader, string.Format("Unexpected token when converting ExpandoObject: {0}", reader.TokenType));
private object ReadList(JsonReader reader, JsonSerializer serializer)
IList<object> list = new List<object>();
switch (reader.TokenType)
object v = ReadValue(reader, serializer);
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject.");
private object ReadObject(JsonReader reader, JsonSerializer serializer)
if (serializer.TypeNameHandling != TypeNameHandling.None)
var obj = JObject.Load(reader);
Type polymorphicType = null;
var polymorphicTypeString = (string)obj["$type"];
if (polymorphicTypeString != null)
if (serializer.TypeNameHandling != TypeNameHandling.None)
string typeName, assemblyName;
ReflectionUtils.SplitFullyQualifiedTypeName(polymorphicTypeString, out typeName, out assemblyName);
polymorphicType = serializer.Binder.BindToType(assemblyName, typeName);
if (polymorphicType == null || polymorphicType == typeof(ExpandoObject))
using (var subReader = obj.CreateReader())
return ReadExpandoObject(subReader, serializer);
using (var subReader = obj.CreateReader())
return serializer.Deserialize(subReader, polymorphicType);
return ReadExpandoObject(reader, serializer);
private object ReadExpandoObject(JsonReader reader, JsonSerializer serializer)
IDictionary<string, object> expandoObject = new ExpandoObject();
switch (reader.TokenType)
case JsonToken.PropertyName:
string propertyName = reader.Value.ToString();
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject.");
object v = ReadValue(reader, serializer);
expandoObject[propertyName] = v;
case JsonToken.EndObject:
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject.");
public override bool CanConvert(Type objectType)
return (objectType == typeof(ExpandoObject));
public override bool CanWrite
internal static class JsonTokenUtils
public static bool IsPrimitiveToken(this JsonToken token)
case JsonToken.Undefined:
internal static class JsonReaderExtensions
public static bool MoveToContent(this JsonReader reader)
throw new ArgumentNullException();
JsonToken t = reader.TokenType;
while (t == JsonToken.None || t == JsonToken.Comment)
internal static class JsonSerializationExceptionHelper
public static JsonSerializationException Create(this JsonReader reader, string format, params object[] args)
var lineInfo = reader as IJsonLineInfo;
var path = (reader == null ? null : reader.Path);
var message = string.Format(CultureInfo.InvariantCulture, format, args);
if (!message.EndsWith(Environment.NewLine, StringComparison.Ordinal))
message = message.Trim();
if (!message.EndsWith(".", StringComparison.Ordinal))
message += string.Format(CultureInfo.InvariantCulture, "Path '{0}'", path);
if (lineInfo != null && lineInfo.HasLineInfo())
message += string.Format(CultureInfo.InvariantCulture, ", line {0}, position {1}", lineInfo.LineNumber, lineInfo.LinePosition);
return new JsonSerializationException(message);
internal static class ReflectionUtils
public static void SplitFullyQualifiedTypeName(string fullyQualifiedTypeName, out string typeName, out string assemblyName)
int? assemblyDelimiterIndex = GetAssemblyDelimiterIndex(fullyQualifiedTypeName);
if (assemblyDelimiterIndex != null)
typeName = fullyQualifiedTypeName.Substring(0, assemblyDelimiterIndex.GetValueOrDefault()).Trim();
assemblyName = fullyQualifiedTypeName.Substring(assemblyDelimiterIndex.GetValueOrDefault() + 1, fullyQualifiedTypeName.Length - assemblyDelimiterIndex.GetValueOrDefault() - 1).Trim();
typeName = fullyQualifiedTypeName;
private static int? GetAssemblyDelimiterIndex(string fullyQualifiedTypeName)
for (int i = 0; i < fullyQualifiedTypeName.Length; i++)
char current = fullyQualifiedTypeName[i];
public decimal A { get; set; }
public string C { get; set; }
public List<Dictionary<string, Model2>> Items { get; set; }
public List<Dictionary<string, Model3>> Items { get; set; }
static ExpandoObject CreateExpandoObject()
var model1 = new Model1 { A = 0.001m };
var model2 = new Model2 { C = "something" };
Items = new List<Dictionary<string, Model2>>
new Dictionary<string, Model2>
{ "A Model2", new Model2 { C = "something else" } },
Items = new List<Dictionary<string, Model3>>
new Dictionary<string, Model3>
IDictionary<string, object> expando = new ExpandoObject();
expando["model1"] = model1;
expando["model2"] = model2;
expando["model4"] = new [] { model4 }.ToList();
IDictionary<string, object> outerExpando = new ExpandoObject();
outerExpando["inner"] = expando;
return (ExpandoObject)outerExpando;
static void Test(TypeNameHandling typeNameHandling)
ConsoleAndDebug.WriteLine("Testing: TypeNameHandling." + typeNameHandling.ToString() + ":");
ConsoleAndDebug.WriteLine("");
var expando = CreateExpandoObject();
var settings = new JsonSerializerSettings
Formatting = Newtonsoft.Json.Formatting.Indented,
TypeNameHandling = typeNameHandling,
TypeNameAssemblyFormat = FormatterAssemblyStyle.Full,
Converters = new [] { new TypeNameHandlingExpandoObjectConverter() },
var json = JsonConvert.SerializeObject(expando, settings);
ConsoleAndDebug.WriteLine(json);
var expando2 = JsonConvert.DeserializeObject<ExpandoObject>(json, settings);
var json2 = JsonConvert.SerializeObject(expando2, settings);
if (!new ExpandoObjectComparer().Equals(expando, expando2))
throw new InvalidOperationException("!new ExpandoObjectComparer().Equals(expando, expando2)");
if (!JToken.DeepEquals(JToken.Parse(json), JToken.Parse(json2)))
throw new InvalidOperationException("!JToken.DeepEquals(JToken.Parse(json), JToken.Parse(json2))");
ConsoleAndDebug.WriteLine("Original and round-tripped ExpandoObjects are identical and have identically typed properties.");
public static void Test()
ConsoleAndDebug.WriteLine("");
Test(TypeNameHandling.Objects);
ConsoleAndDebug.WriteLine("");
Test(TypeNameHandling.All);
ConsoleAndDebug.WriteLine("");
Test(TypeNameHandling.None);
ConsoleAndDebug.WriteLine("Caught expected exception: " + ex.Message);
ConsoleAndDebug.WriteLine("Original and round-tripped ExpandoObjects were not identical as expected.");
ConsoleAndDebug.WriteLine("");
Test(TypeNameHandling.Auto);
""$type"": ""MyType, MyAssembly"",
public class ExpandoObjectComparer : IEqualityComparer<ExpandoObject>
#region IEqualityComparer<ExpandoObject> Members
bool ValueComparer(object x, object y)
if (object.ReferenceEquals(x, y))
else if (x == null || y == null)
if (x is IList && y is IList)
if (xList.Count != yList.Count)
for (int i = 0; i < xList.Count; i++)
if (!ValueComparer(xList[i], yList[i]))
if (x.GetType() != y.GetType())
if (x is ExpandoObject && y is ExpandoObject)
return Equals((ExpandoObject)x, (ExpandoObject)y);
return JToken.DeepEquals(JToken.FromObject(x), JToken.FromObject(y));
public bool Equals(ExpandoObject xExpando, ExpandoObject yExpando)
IDictionary<string, object> x = xExpando;
IDictionary<string, object> y = yExpando;
if (x.Keys.Except(y.Keys).Any())
if (y.Keys.Except(x.Keys).Any())
if (!ValueComparer(pair.Value, y[pair.Key]))
public int GetHashCode(ExpandoObject obj)
IDictionary<string, object> dict = obj;
return dict.Count.GetHashCode();
public static void Main()
ConsoleAndDebug.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);
public static class ConsoleAndDebug
public static void Write(object s)
public static void WriteLine(object s)