Commits

Anonymous committed fd10ff8

Added feature of maintaining object references and updated documentation.

  • Participants
  • Parent commits 6c9d5da

Comments (0)

Files changed (14)

File Json.Tests/Json/Serialization/JsonSerializerTest.cs

 		public void Deserialize_List_Successful()
 		{
 			JsonSerializationTypeRegistry.RegisterListType<int>();
-			JsonValue json = new JsonArray { 4, 3, 5, 6 };
-			var expected = new List<int> { 4, 3, 5, 6 };
+			JsonValue json = new JsonArray {4, 3, 5, 6};
+			var expected = new List<int> {4, 3, 5, 6};
 			var actual = _serializer.Deserialize<List<int>>(json);
 			Assert.AreEqual(expected.Count, actual.Count);
 			for (int i = 0; i < expected.Count; i++)
 		public void Deserialize_Dictionary_Successful()
 		{
 			JsonSerializationTypeRegistry.RegisterDictionaryType<string, double>();
-			var expected = new Dictionary<string, double> { { "four", 4 }, { "three", 3 }, { "five", 5 }, { "six", 6 } };
+			var expected = new Dictionary<string, double> {{"four", 4}, {"three", 3}, {"five", 5}, {"six", 6}};
 			JsonValue json = new JsonArray
 			                     	{
 			                     		new JsonObject {{"Key", "four"}, {"Value", 4}},
 		public void Deserialize_Queue_Successful()
 		{
 			JsonSerializationTypeRegistry.RegisterQueueType<int>();
-			JsonValue json = new JsonArray { 4, 3, 5, 6 };
+			JsonValue json = new JsonArray {4, 3, 5, 6};
 			var expected = new Queue<int>();
 			expected.Enqueue(4);
 			expected.Enqueue(3);
 		public void Deserialize_Stack_Successful()
 		{
 			JsonSerializationTypeRegistry.RegisterStackType<int>();
-			JsonValue json = new JsonArray { 4, 3, 5, 6 };
+			JsonValue json = new JsonArray {4, 3, 5, 6};
 			var expected = new Stack<int>();
 			expected.Push(4);
 			expected.Push(3);
 							{"OtherProp", Math.PI}
 			           	};
 			var expected = new ObjectWithBasicProps
-			{
-				StringProp = "stringValue",
-				IntProp = 42,
-				DoubleProp = 6.0,
-				BoolProp = true
-			};
+			               	{
+			               		StringProp = "stringValue",
+			               		IntProp = 42,
+			               		DoubleProp = 6.0,
+			               		BoolProp = true
+			               	};
 			var actual = _serializer.Deserialize<ObjectWithBasicProps>(json);
 			Assert.AreEqual(expected, actual);
 		}
 				_serializer.Options = null;
 			}
 		}
+		[TestMethod]
+		public void Deserialize_CircularStructure_MaintainsReferences()
+		{
+			var json = JsonValue.Parse(@"{""LoopProperty"":{""LoopProperty"":{""#Ref"":""f3a2993b-9b0c-4296-872e-95a9210295f4""},""StringProp"":""stringValueB"",""IntProp"":6,""BoolProp"":true},""StringProp"":""stringValueA"",""IntProp"":42,""#Define"":""f3a2993b-9b0c-4296-872e-95a9210295f4""}");
+			var expected = new ObjectWithExtendedProps
+			{
+				StringProp = "stringValueA",
+				IntProp = 42
+			};
+			var obj2 = new ObjectWithExtendedProps
+			{
+				StringProp = "stringValueB",
+				IntProp = 6,
+				BoolProp = true,
+				LoopProperty = expected
+			};
+			expected.LoopProperty = obj2;
+
+			var actual = _serializer.Deserialize<ObjectWithExtendedProps>(json);
+
+			Assert.IsNotNull(actual);
+			Assert.AreEqual(expected.StringProp, actual.StringProp);
+			Assert.IsNotNull(actual.LoopProperty);
+			Assert.AreEqual(expected.LoopProperty.StringProp, actual.LoopProperty.StringProp);
+			Assert.IsNotNull(actual.LoopProperty.LoopProperty);
+			Assert.AreSame(actual, actual.LoopProperty.LoopProperty);
+
+		}
 		#endregion
 
 		#region Serialize Tests
 			                      	};
 			// DoubleProp remains default
 			var obj = new ObjectWithBasicProps
-			{
-				StringProp = "stringValue",
-				IntProp = 42,
-				BoolProp = true
-			};
+			          	{
+			          		StringProp = "stringValue",
+			          		IntProp = 42,
+			          		BoolProp = true
+			          	};
 			JsonValue expected = new JsonObject
 									{
 										{"StringProp", "stringValue"},
 			_serializer.Options = null;
 			Assert.AreEqual(expected, actual);
 		}
+		[TestMethod]
+		public void Serialize_CircularStructure_SerializesWithReference()
+		{
+			var obj = new ObjectWithExtendedProps
+			          	{
+			          		StringProp = "stringValue",
+			          		IntProp = 42,
+			          		BoolProp = true
+			          	};
+			var obj2 = new ObjectWithExtendedProps
+			{
+				StringProp = "stringValue",
+				IntProp = 42,
+				BoolProp = true,
+				LoopProperty = obj
+			};
+			obj.LoopProperty = obj2;
+
+			var actual = _serializer.Serialize(obj);
+
+			Assert.IsNotNull(actual);
+			Assert.AreEqual(JsonValueType.Object, actual.Type);
+			Assert.IsTrue(actual.Object.ContainsKey("#Define"));
+			Assert.IsTrue(actual.Object.ContainsKey("LoopProperty"));
+			Assert.AreEqual(JsonValueType.Object, actual.Object["LoopProperty"].Type);
+			Assert.IsTrue(actual.Object["LoopProperty"].Object.ContainsKey("LoopProperty"));
+			Assert.AreEqual(JsonValueType.Object, actual.Object["LoopProperty"].Object["LoopProperty"].Type);
+			Assert.IsTrue(actual.Object["LoopProperty"].Object["LoopProperty"].Object.ContainsKey("#Ref"));
+			Assert.AreEqual(actual.Object["#Define"], actual.Object["LoopProperty"].Object["LoopProperty"].Object["#Ref"]);
+		}
 		#endregion
 	}
 }

File Json.Tests/Manatee.Tests.csproj

     <Compile Include="Test References\JsonCompatibleClass.cs" />
     <Compile Include="Test References\ObjectWithAbstractAndInterfaceProps.cs" />
     <Compile Include="Test References\ObjectWithBasicProps.cs" />
+    <Compile Include="Test References\ObjectWithExtendedProps.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Json\Manatee.Json.csproj">

File Json.Tests/Test References/ObjectWithExtendedProps.cs

+/***************************************************************************************
+
+	Copyright 2012 Greg Dennis
+
+	   Licensed under the Apache License, Version 2.0 (the "License");
+	   you may not use this file except in compliance with the License.
+	   You may obtain a copy of the License at
+
+		 http://www.apache.org/licenses/LICENSE-2.0
+
+	   Unless required by applicable law or agreed to in writing, software
+	   distributed under the License is distributed on an "AS IS" BASIS,
+	   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+	   See the License for the specific language governing permissions and
+	   limitations under the License.
+ 
+	File Name:		ObjectWithBasicProps.cs
+	Namespace:		Manatee.Tests.Test_References
+	Class Name:		ObjectWithBasicProps
+	Purpose:		Basic class that contains basic properties of various
+					accessibility to be used in testing the Manatee.Json
+					library.
+
+***************************************************************************************/
+using System;
+using Manatee.Json.Attributes;
+
+namespace Manatee.Tests.Test_References
+{
+	public class ObjectWithExtendedProps : ObjectWithBasicProps
+	{
+		public ObjectWithExtendedProps LoopProperty { get; set; }
+
+		#region Equality Testing
+
+		public override bool Equals(object obj)
+		{
+			if (ReferenceEquals(null, obj)) return false;
+			if (ReferenceEquals(this, obj)) return true;
+			if (obj.GetType() != typeof(ObjectWithExtendedProps)) return false;
+			return Equals((ObjectWithExtendedProps)obj);
+		}
+
+		public bool Equals(ObjectWithExtendedProps other)
+		{
+			if (ReferenceEquals(null, other)) return false;
+			if (ReferenceEquals(this, other)) return true;
+			return base.Equals(other) && Equals(other.LoopProperty, LoopProperty);
+		}
+
+		public override int GetHashCode()
+		{
+			unchecked
+			{
+				return (base.GetHashCode() * 397) ^ (LoopProperty != null ? LoopProperty.GetHashCode() : 0);
+			}
+		}
+		#endregion
+
+	}
+}

File Json.sln.docstates.suo

Binary file modified.

File Json.suo

Binary file modified.

File Json/Helpers/PropertyReference.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace Manatee.Json.Helpers
+{
+	class PropertyReference
+	{
+		public PropertyInfo Info { get; set; }
+		public object Owner { get; set; }
+	}
+}

File Json/Helpers/SerializerMethodPair.cs

 
 		private static MethodInfo GetTypedSerializeMethod(Type type)
 		{
-			return typeof(JsonSerializer).GetMethod("Serialize").MakeGenericMethod(type);
+			return typeof(JsonSerializer).GetMethod("ManagedSerialize", BindingFlags.Instance | BindingFlags.NonPublic)
+										 .MakeGenericMethod(type);
 		}
 		private static MethodInfo GetTypedDeserializeMethod(Type type)
 		{
-			return typeof(JsonSerializer).GetMethod("Deserialize").MakeGenericMethod(type);
+			return typeof(JsonSerializer).GetMethod("ManagedDeserialize", BindingFlags.Instance | BindingFlags.NonPublic)
+										 .MakeGenericMethod(type);
 		}
 	}
 }

File Json/Helpers/SerializerReferenceCache.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Manatee.Json.Enumerations;
+
+namespace Manatee.Json.Helpers
+{
+	class SerializerReferenceCache : List<SerializerReferenceRecord>
+	{
+		private const string DefKey = "#Define";
+
+		public SerializerReferenceRecord FindRecord(object obj)
+		{
+			if (obj is string) return null;
+			return this.Where(record => ReferenceEquals(record.Object, obj)).FirstOrDefault();
+		}
+		public void AddReference(string guid, object owner, PropertyInfo prop)
+		{
+			var g = Guid.Parse(guid);
+			var record = this.Where(r => r.ReferenceID.Equals(g)).FirstOrDefault();
+			if (record == null) return;
+			record.References.Add(new PropertyReference {Owner = owner, Info = prop});
+		}
+		public void ReconcileJsonReferences()
+		{
+			var records = this.Where(record => record.IsReferenced);
+			foreach (var record in records)
+			{
+				switch (record.Json.Type)
+				{
+					case JsonValueType.Object:
+						record.Json.Object.Add(DefKey, record.ReferenceID.ToString());
+						break;
+					case JsonValueType.Array:
+						record.Json.Array.Add(new JsonObject{{DefKey, record.ReferenceID.ToString()}});
+						break;
+					default:
+						throw new ArgumentOutOfRangeException();
+				}
+			}
+		}
+		public void ReconcileObjectReferences()
+		{
+			foreach (var record in this)
+			{
+				foreach (var prop in record.References)
+				{
+					prop.Info.SetValue(prop.Owner, record.Object, null);
+				}
+			}
+		}
+	}
+}

File Json/Helpers/SerializerReferenceRecord.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace Manatee.Json.Helpers
+{
+	class SerializerReferenceRecord
+	{
+		private Guid _guid;
+
+		public object Object { get; set; }
+		public Guid ReferenceID { get { return _guid; } }
+		public JsonValue Json { get; set; }
+		public bool IsReferenced { get; set; }
+		public List<PropertyReference> References { get; private set; }
+
+		public SerializerReferenceRecord()
+		{
+			_guid = Guid.NewGuid();
+			References = new List<PropertyReference>();
+		}
+		public SerializerReferenceRecord(string guid)
+		{
+			_guid = Guid.Parse(guid);
+			References = new List<PropertyReference>();
+		}
+	}
+}

File Json/JsonValue.cs

 		///<returns></returns>
 		public static bool operator !=(JsonValue a, JsonValue b)
 		{
-			return !a.Equals(b);
+			return !Equals(a, b);
 		}
 
 		internal static JsonValue Parse(string source, ref int index)

File Json/Manatee.Json.csproj

     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Helpers\PropertyReference.cs" />
+    <Compile Include="Helpers\SerializerReferenceCache.cs" />
+    <Compile Include="Helpers\SerializerReferenceRecord.cs" />
     <Compile Include="Helpers\XmlNamespaceRegistry.cs" />
     <Compile Include="Helpers\XmlNamespacePair.cs" />
     <Compile Include="Helpers\CharacterConverter.cs" />

File Json/Serialization/JsonSerializationTypeRegistry.cs

 using System.Linq;
 using Manatee.Json.Enumerations;
 using Manatee.Json.Exceptions;
+using Manatee.Json.Helpers;
 
 namespace Manatee.Json.Serialization
 {
 		/// <returns>The JSON representation of the List&lt;T&gt;.</returns>
 		public static JsonValue EncodeGenericList<T>(List<T> list)
 		{
+			var cache = new SerializerReferenceCache();
 			var array = new JsonArray();
-			array.AddRange(list.Select(item => _serializer.Serialize(item)));
+			array.AddRange(list.Select(item => _serializer.ManagedSerialize(item, cache)));
+			cache.ReconcileJsonReferences();
 			return array;
 		}
 		/// <summary>
 		/// <returns>The List&lt;T&gt; object.</returns>
 		public static List<T> DecodeGenericList<T>(JsonValue json)
 		{
+			var cache = new SerializerReferenceCache();
 			var list = new List<T>();
-			list.AddRange(json.Array.Select(jv => _serializer.Deserialize<T>(jv)));
+			list.AddRange(json.Array.Select(jv => _serializer.ManagedDeserialize<T>(jv, cache)));
+			cache.ReconcileObjectReferences();
 			return list;
 		}
 		#endregion
 		/// <returns>The JSON representation of the Dictionary&lt;T&gt;.</returns>
 		public static JsonValue EncodeGenericDictionary<TKey, TValue>(Dictionary<TKey, TValue> dict)
 		{
+			var cache = new SerializerReferenceCache();
 			var array = new JsonArray();
 			array.AddRange(dict.Select(item => (JsonValue)(new JsonObject
 			                                               	{
-			                                               		{"Key", _serializer.Serialize(item.Key)},
-																{"Value", _serializer.Serialize(item.Value)}
+			                                               		{"Key", _serializer.ManagedSerialize(item.Key, cache)},
+																{"Value", _serializer.ManagedSerialize(item.Value, cache)}
 			                                               	})));
+			cache.ReconcileJsonReferences();
 			return array;
 		}
 		/// <summary>
 		/// <returns>The Dictionary&lt;T&gt; object.</returns>
 		public static Dictionary<TKey, TValue> DecodeGenericDictionary<TKey, TValue>(JsonValue json)
 		{
+			var cache = new SerializerReferenceCache();
 			var dict = new Dictionary<TKey, TValue>();
 			foreach (var jv in json.Array)
 			{
-				dict.Add(_serializer.Deserialize<TKey>(jv.Object["Key"]),
-						 _serializer.Deserialize<TValue>(jv.Object["Value"]));
+				dict.Add(_serializer.ManagedDeserialize<TKey>(jv.Object["Key"], cache),
+						 _serializer.ManagedDeserialize<TValue>(jv.Object["Value"], cache));
 			}
+			cache.ReconcileObjectReferences();
 			return dict;
 		}
 		#endregion
 		/// <returns>The JSON representation of the Queue&lt;T&gt;.</returns>
 		public static JsonValue EncodeGenericQueue<T>(Queue<T> queue)
 		{
+			var cache = new SerializerReferenceCache();
 			var array = new JsonArray();
 			for (int i = 0; i < queue.Count; i++)
 			{
-				array.Add(_serializer.Serialize(queue.ElementAt(i)));
+				array.Add(_serializer.ManagedSerialize(queue.ElementAt(i), cache));
 			}
+			cache.ReconcileJsonReferences();
 			return array;
 		}
 		/// <summary>
 		/// <returns>The Queue&lt;T&gt; object.</returns>
 		public static Queue<T> DecodeGenericQueue<T>(JsonValue json)
 		{
+			var cache = new SerializerReferenceCache();
 			var queue = new Queue<T>();
 			for (int i = 0; i < json.Array.Count; i++)
 			{
-				queue.Enqueue(_serializer.Deserialize<T>(json.Array[i]));
+				queue.Enqueue(_serializer.ManagedDeserialize<T>(json.Array[i], cache));
 			}
+			cache.ReconcileObjectReferences();
 			return queue;
 		}
 		#endregion
 		/// <returns>The JSON representation of the Stack&lt;T&gt;.</returns>
 		public static JsonValue EncodeGenericStack<T>(Stack<T> stack)
 		{
+			var cache = new SerializerReferenceCache();
 			var array = new JsonArray();
 			for (int i = 0; i < stack.Count; i++)
 			{
-				array.Add(_serializer.Serialize(stack.ElementAt(i)));
+				array.Add(_serializer.ManagedSerialize(stack.ElementAt(i), cache));
 			}
+			cache.ReconcileJsonReferences();
 			return array;
 		}
 		/// <summary>
 		/// <returns>The Stack&lt;T&gt; object.</returns>
 		public static Stack<T> DecodeGenericStack<T>(JsonValue json)
 		{
+			var cache = new SerializerReferenceCache();
 			var stack = new Stack<T>();
 			for (int i = 0; i < json.Array.Count; i++)
 			{
-				stack.Push(_serializer.Deserialize<T>(json.Array[i]));
+				stack.Push(_serializer.ManagedDeserialize<T>(json.Array[i], cache));
 			}
+			cache.ReconcileObjectReferences();
 			return stack;
 		}
 		#endregion

File Json/Serialization/JsonSerializer.cs

 	{
 		private const string TypeKey = "#Type";
 		private const string ValueKey = "#Value";
+		private const string RefKey = "#Ref";
+		private const string DefKey = "#Define";
 
 		/// <summary>
 		/// Gets or sets a set of options for the serializer.
 		/// <returns>The JSON representation of the object.</returns>
 		public JsonValue Serialize<T>(T obj)
 		{
-			VerifyOptions();
-			if (typeof(IJsonCompatible).IsAssignableFrom(typeof(T)))
-				return ((IJsonCompatible) obj).ToJson();
-			if (EqualsDefaultValue(obj) && !Options.EncodeDefaultValues) return JsonValue.Null;
-			var tryPrimitive = PrimitiveMapper.MapToJson(obj);
-			if (tryPrimitive != JsonValue.Null)
-				return tryPrimitive;
-			JsonValue json;
-			return this.TryEncode(obj, out json) ? json : AutoSerializeObject(obj);
-			//var converter = JsonSerializationTypeRegistry.GetToJsonConverter<T>();
-			//return converter == null ? AutoSerializeObject(obj) : converter(obj);
+			var cache = new SerializerReferenceCache();
+			var json = ManagedSerialize(obj, cache);
+			cache.ReconcileJsonReferences();
+			return json;
 		}
 		/// <summary>
 		/// Serializes the public static properties of a type to a JSON structure.
 		/// <returns>The JSON representation of the type.</returns>
 		public JsonValue SerializeType<T>()
 		{
+			var cache = new SerializerReferenceCache();
 			VerifyOptions();
 			var json = new JsonObject();
 			var propertyInfoList = typeof(T).GetProperties(BindingFlags.Static | BindingFlags.Public)
-											 .Where(p => p.GetSetMethod() != null)
-											 .Where(p => p.GetGetMethod() != null)
-											 .Where(p => p.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Count() == 0);
+											.Where(p => p.GetSetMethod() != null)
+											.Where(p => p.GetGetMethod() != null)
+											.Where(p => p.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Count() == 0);
 			foreach (var propertyInfo in propertyInfoList)
 			{
 				var value = propertyInfo.GetValue(null, null);
 								? value.GetType()
 								: propertyInfo.PropertyType;
 					var serialize = SerializerCache.Instance.GetSerializer(type);
-					json.Add(propertyInfo.Name, (JsonValue)serialize.Invoke(this, new[] { value }));
+					json.Add(propertyInfo.Name, (JsonValue) serialize.Invoke(this, new[] {value, cache}));
 				}
 			}
+			cache.ReconcileJsonReferences();
 			return json.Count == 0 ? JsonValue.Null : json;
 		}
 		/// <summary>
 		/// <returns>The deserialized object.</returns>
 		public T Deserialize<T>(JsonValue json)
 		{
-			VerifyOptions();
-			if (typeof(IJsonCompatible).IsAssignableFrom(typeof(T)))
-			{
-				var obj = (T)Activator.CreateInstance(typeof(T));
-				((IJsonCompatible)obj).FromJson(json);
-				return obj;
-			}
-			if (json == JsonValue.Null)
-				return default(T);
-			if (PrimitiveMapper.IsPrimitive(typeof(T)))
-				return PrimitiveMapper.MapFromJson<T>(json);
-			T ret;
-			return this.TryDecode(json, out ret) ? ret : AutoDeserializeObject<T>(json);
-			//var converter = JsonSerializationTypeRegistry.GetFromJsonConverter<T>();
-			//return converter == null ? AutoDeserializeObject<T>(json) : converter(json);
+			var cache = new SerializerReferenceCache();
+			var obj = ManagedDeserialize<T>(json, cache);
+			cache.ReconcileObjectReferences();
+			return obj;
 		}
 		/// <summary>
 		/// Deserializes a JSON structure to the public static properties of a type.
 		/// <param name="json">The JSON representation of the type.</param>
 		public void DeserializeType<T>(JsonValue json)
 		{
+			var cache = new SerializerReferenceCache();
 			VerifyOptions();
 			if (json == JsonValue.Null)
 				return;
 			var propertyInfoList = typeof(T).GetProperties(BindingFlags.Static | BindingFlags.Public)
-											 .Where(p => p.GetSetMethod() != null)
-											 .Where(p => p.GetGetMethod() != null)
-											 .Where(p => p.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Count() == 0);
+											.Where(p => p.GetSetMethod() != null)
+											.Where(p => p.GetGetMethod() != null)
+											.Where(p => p.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Count() == 0);
 			foreach (var propertyInfo in propertyInfoList)
 			{
 				if (json.Object.ContainsKey(propertyInfo.Name))
 				{
 					var deserialize = SerializerCache.Instance.GetDeserializer(propertyInfo.PropertyType);
-					propertyInfo.SetValue(null, deserialize.Invoke(this, new[] { json.Object[propertyInfo.Name] }), null);
+					propertyInfo.SetValue(null, deserialize.Invoke(this, new object[] {json.Object[propertyInfo.Name], cache}), null);
 					json.Object.Remove(propertyInfo.Name);
 				}
 			}
+			cache.ReconcileObjectReferences();
 			if (json.Object.Count > 0)
 				throw new TypeDoesNotContainPropertyException(typeof(T), json);
 		}
 		#endregion
 
 		#region Support Methods
+		internal JsonValue ManagedSerialize<T>(T obj, SerializerReferenceCache cache)
+		{
+			var match = cache.FindRecord(obj);
+			if (match != null)
+			{
+				match.IsReferenced = true;
+				return new JsonObject {{RefKey, match.ReferenceID.ToString()}};
+			}
+			match = new SerializerReferenceRecord { Object = obj };
+			cache.Add(match);
+			VerifyOptions();
+			if (typeof(IJsonCompatible).IsAssignableFrom(typeof(T)))
+			{
+				match.Json = ((IJsonCompatible)obj).ToJson();
+			}
+			else
+			{
+				if (EqualsDefaultValue(obj) && !Options.EncodeDefaultValues) return JsonValue.Null;
+				match.Json = PrimitiveMapper.MapToJson(obj);
+				if (match.Json == JsonValue.Null)
+				{
+					JsonValue json;
+					match.Json = this.TryEncode(obj, out json) ? json : AutoSerializeObject(obj, cache);
+				}
+			}
+			return match.Json;
+		}
+		internal T ManagedDeserialize<T>(JsonValue json, SerializerReferenceCache cache)
+		{
+			SerializerReferenceRecord match = null;
+			switch (json.Type)
+			{
+				case JsonValueType.Object:
+					if (json.Object.ContainsKey(DefKey))
+						cache.Add(match = new SerializerReferenceRecord(json.Object[DefKey].String));
+					break;
+				case JsonValueType.Array:
+					var def = json.Array.Where(jv => (jv.Type == JsonValueType.Object) &&
+													 (jv.Object.Count == 1) &&
+					                                 jv.Object.ContainsKey(DefKey))
+										.FirstOrDefault();
+					if (def != null)
+						cache.Add(match = new SerializerReferenceRecord(def.Object[DefKey].String));
+					break;
+			}
+			VerifyOptions();
+			T obj;
+			if (typeof(IJsonCompatible).IsAssignableFrom(typeof(T)))
+			{
+				obj = Activator.CreateInstance<T>();
+				((IJsonCompatible)obj).FromJson(json);
+			}
+			else if (json == JsonValue.Null)
+			{
+				obj = default(T);
+			}
+			else if (PrimitiveMapper.IsPrimitive(typeof(T)))
+			{
+				obj = PrimitiveMapper.MapFromJson<T>(json);
+			}
+			else if (!this.TryDecode(json, out obj))
+			{
+				obj = AutoDeserializeObject<T>(json, cache);
+			}
+			if (match != null)
+				match.Object = obj;
+			return obj;
+		}
 		private static bool EqualsDefaultValue<T>(T value)
 		{
 			return EqualityComparer<T>.Default.Equals(value, default(T));
 			if (Options == null)
 				Options = JsonSerializerOptions.Default;
 		}
-		private JsonValue AutoSerializeObject<T>(T obj)
+		private JsonValue AutoSerializeObject<T>(T obj, SerializerReferenceCache cache)
 		{
 			var json = new JsonObject();
 			var propertyInfoList = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
 					        ? value.GetType()
 					        : propertyInfo.PropertyType;
 				var serialize = SerializerCache.Instance.GetSerializer(type);
-				var jsonProp = (JsonValue) serialize.Invoke(this, new[] {value});
+				var jsonProp = (JsonValue) serialize.Invoke(this, new[] {value, cache});
 				if ((jsonProp == JsonValue.Null) && !Options.EncodeDefaultValues) continue;
 				json.Add(propertyInfo.Name,
 				         type == propertyInfo.PropertyType
 			}
 			return json.Count == 0 ? JsonValue.Null : json;
 		}
-		private T AutoDeserializeObject<T>(JsonValue json)
+		private T AutoDeserializeObject<T>(JsonValue json, SerializerReferenceCache cache)
 		{
 			var propertyInfoList = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
 											.Where(p => p.GetSetMethod() != null)
 											.Where(p => p.GetGetMethod() != null)
 											.Where(p => p.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Count() == 0);
-			var obj = (T)Activator.CreateInstance(typeof(T));
+			var obj = Activator.CreateInstance<T>();
 			foreach (var propertyInfo in propertyInfoList)
 			{
 				if (json.Object.ContainsKey(propertyInfo.Name))
 				{
 					var value = json.Object[propertyInfo.Name];
-					if (value.Type == JsonValueType.Object)
+					if ((value.Type == JsonValueType.Object) && (value.Object.ContainsKey(TypeKey)))
 					{
 						var instanceType = Type.GetType(value.Object[TypeKey].String);
 						var instanceJson = value.Object[ValueKey];
 						var deserialize = SerializerCache.Instance.GetDeserializer(instanceType);
-						propertyInfo.SetValue(obj, deserialize.Invoke(this, new[] { instanceJson }), null);
+						propertyInfo.SetValue(obj, deserialize.Invoke(this, new object[] {instanceJson, cache}), null);
+					}
+					else if ((value.Type == JsonValueType.Object) && (value.Object.ContainsKey(RefKey)))
+					{
+						cache.AddReference(value.Object[RefKey].String, obj, propertyInfo);
 					}
 					else
 					{
 						var deserialize = SerializerCache.Instance.GetDeserializer(propertyInfo.PropertyType);
-						propertyInfo.SetValue(obj, deserialize.Invoke(this, new[] { json.Object[propertyInfo.Name] }), null);
+						propertyInfo.SetValue(obj, deserialize.Invoke(this, new object[] { json.Object[propertyInfo.Name], cache }), null);
 					}
 					json.Object.Remove(propertyInfo.Name);
 				}

File Manatee.Json - ReadMe.docx

Binary file modified.