-Fixed error when reusing JsonSerializer with preserve references enabled

This commit is contained in:
JamesNK
2011-03-22 05:15:55 +00:00
parent 9e1c9e96fb
commit 6f0c382ba7
8 changed files with 161 additions and 59 deletions
@@ -4203,5 +4203,20 @@ keyword such as type of business.""
Assert.AreEqual(44.4, p.Height);
Assert.AreEqual(4d, p.Price);
}
[Test]
[ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "Error converting value {null} to type 'System.DateTime'.")]
public void DeserializeNullDateTimeValueTest()
{
JsonConvert.DeserializeObject("null", typeof(DateTime));
}
[Test]
public void DeserializeNullNullableDateTimeValueTest()
{
object dateTime = JsonConvert.DeserializeObject("null", typeof(DateTime?));
Assert.IsNull(dateTime);
}
}
}
@@ -25,6 +25,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
@@ -283,7 +284,7 @@ namespace Newtonsoft.Json.Tests.Serialization
{
CircularReferenceClass circularReferenceClass = (CircularReferenceClass)value;
string reference = serializer.ReferenceResolver.GetReference(circularReferenceClass);
string reference = serializer.ReferenceResolver.GetReference(serializer, circularReferenceClass);
JObject me = new JObject();
me["$id"] = new JValue(reference);
@@ -309,7 +310,7 @@ namespace Newtonsoft.Json.Tests.Serialization
else
{
string reference = (string)o["$ref"];
return serializer.ReferenceResolver.ResolveReference(reference);
return serializer.ReferenceResolver.ResolveReference(serializer, reference);
}
}
@@ -802,5 +803,61 @@ namespace Newtonsoft.Json.Tests.Serialization
User deserializedUser = JsonConvert.DeserializeObject<User>(json, serializerSettings);
Assert.IsNotNull(deserializedUser);
}
[Test]
public void PreserveReferencesHandlingWithReusedJsonSerializer()
{
MyClass c = new MyClass();
IList<MyClass> myClasses1 = new List<MyClass>
{
c,
c
};
var ser = new JsonSerializer()
{
PreserveReferencesHandling = PreserveReferencesHandling.All
};
MemoryStream ms = new MemoryStream();
using (var sw = new StreamWriter(ms))
using (var writer = new JsonTextWriter(sw) { Formatting = Formatting.Indented })
{
ser.Serialize(writer, myClasses1);
}
byte[] data = ms.ToArray();
string json = Encoding.UTF8.GetString(data, 0, data.Length);
Assert.AreEqual(@"{
""$id"": ""1"",
""$values"": [
{
""$id"": ""2"",
""PreProperty"": 0,
""PostProperty"": 0
},
{
""$ref"": ""2""
}
]
}", json);
ms = new MemoryStream(data);
IList<MyClass> myClasses2;
using (var sr = new StreamReader(ms))
using (var reader = new JsonTextReader(sr))
{
myClasses2 = ser.Deserialize<IList<MyClass>>(reader);
}
Assert.AreEqual(2, myClasses2.Count);
Assert.AreEqual(myClasses2[0], myClasses2[1]);
Assert.AreNotEqual(myClasses1[0], myClasses2[0]);
}
}
}
@@ -35,72 +35,53 @@ namespace Newtonsoft.Json.Serialization
{
internal class DefaultReferenceResolver : IReferenceResolver
{
private class ReferenceEqualsEqualityComparer : IEqualityComparer<object>
{
bool IEqualityComparer<object>.Equals(object x, object y)
{
return ReferenceEquals(x, y);
}
int IEqualityComparer<object>.GetHashCode(object obj)
{
#if !PocketPC
// put objects in a bucket based on their reference
return RuntimeHelpers.GetHashCode(obj);
#else
// put all objects in the same bucket so ReferenceEquals is called on all
return -1;
#endif
}
}
private int _referenceCount;
private BidirectionalDictionary<string, object> _mappings;
private BidirectionalDictionary<string, object> Mappings
private BidirectionalDictionary<string, object> GetMappings(object context)
{
get
{
// override equality comparer for object key dictionary
// object will be modified as it deserializes and might have mutable hashcode
if (_mappings == null)
_mappings = new BidirectionalDictionary<string, object>(
EqualityComparer<string>.Default,
new ReferenceEqualsEqualityComparer());
JsonSerializerInternalBase internalSerializer;
return _mappings;
}
if (context is JsonSerializerInternalBase)
internalSerializer = (JsonSerializerInternalBase) context;
else if (context is JsonSerializerProxy)
internalSerializer = ((JsonSerializerProxy) context).GetInternalSerializer();
else
throw new Exception("The DefaultReferenceResolver can only be used internally.");
return internalSerializer.DefaultReferenceMappings;
}
public object ResolveReference(string reference)
public object ResolveReference(object context, string reference)
{
object value;
Mappings.TryGetByFirst(reference, out value);
GetMappings(context).TryGetByFirst(reference, out value);
return value;
}
public string GetReference(object value)
public string GetReference(object context, object value)
{
var mappings = GetMappings(context);
string reference;
if (!Mappings.TryGetBySecond(value, out reference))
if (!mappings.TryGetBySecond(value, out reference))
{
_referenceCount++;
reference = _referenceCount.ToString(CultureInfo.InvariantCulture);
Mappings.Add(reference, value);
mappings.Add(reference, value);
}
return reference;
}
public void AddReference(string reference, object value)
public void AddReference(object context, string reference, object value)
{
Mappings.Add(reference, value);
GetMappings(context).Add(reference, value);
}
public bool IsReferenced(object value)
public bool IsReferenced(object context, object value)
{
string reference;
return Mappings.TryGetBySecond(value, out reference);
return GetMappings(context).TryGetBySecond(value, out reference);
}
}
}
}
@@ -33,28 +33,32 @@ namespace Newtonsoft.Json.Serialization
/// <summary>
/// Resolves a reference to its object.
/// </summary>
/// <param name="context">The serialization context.</param>
/// <param name="reference">The reference to resolve.</param>
/// <returns>The object that</returns>
object ResolveReference(string reference);
object ResolveReference(object context, string reference);
/// <summary>
/// Gets the reference for the sepecified object.
/// </summary>
/// <param name="context">The serialization context.</param>
/// <param name="value">The object to get a reference for.</param>
/// <returns>The reference to the object.</returns>
string GetReference(object value);
string GetReference(object context, object value);
/// <summary>
/// Determines whether the specified object is referenced.
/// </summary>
/// <param name="context">The serialization context.</param>
/// <param name="value">The object to test for a reference.</param>
/// <returns>
/// <c>true</c> if the specified object is referenced; otherwise, <c>false</c>.
/// </returns>
bool IsReferenced(object value);
bool IsReferenced(object context, object value);
/// <summary>
/// Adds a reference to the specified object.
/// </summary>
/// <param name="context">The serialization context.</param>
/// <param name="reference">The reference.</param>
/// <param name="value">The object to reference.</param>
void AddReference(string reference, object value);
void AddReference(object context, string reference, object value);
}
}
@@ -24,14 +24,36 @@
#endregion
using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.CompilerServices;
using Newtonsoft.Json.Utilities;
namespace Newtonsoft.Json.Serialization
{
internal abstract class JsonSerializerInternalBase
{
private class ReferenceEqualsEqualityComparer : IEqualityComparer<object>
{
bool IEqualityComparer<object>.Equals(object x, object y)
{
return ReferenceEquals(x, y);
}
int IEqualityComparer<object>.GetHashCode(object obj)
{
#if !PocketPC
// put objects in a bucket based on their reference
return RuntimeHelpers.GetHashCode(obj);
#else
// put all objects in the same bucket so ReferenceEquals is called on all
return -1;
#endif
}
}
private ErrorContext _currentErrorContext;
private BidirectionalDictionary<string, object> _mappings;
internal JsonSerializer Serializer { get; private set; }
@@ -42,6 +64,21 @@ namespace Newtonsoft.Json.Serialization
Serializer = serializer;
}
internal BidirectionalDictionary<string, object> DefaultReferenceMappings
{
get
{
// override equality comparer for object key dictionary
// object will be modified as it deserializes and might have mutable hashcode
if (_mappings == null)
_mappings = new BidirectionalDictionary<string, object>(
EqualityComparer<string>.Default,
new ReferenceEqualsEqualityComparer());
return _mappings;
}
}
protected ErrorContext GetErrorContext(object currentObject, object member, Exception error)
{
if (_currentErrorContext == null)
@@ -315,7 +315,7 @@ namespace Newtonsoft.Json.Serialization
if (reader.TokenType == JsonToken.PropertyName)
throw new JsonSerializationException("Additional content found in JSON reference object. A JSON reference object should only have a {0} property.".FormatWith(CultureInfo.InvariantCulture, JsonTypeReflector.RefPropertyName));
return Serializer.ReferenceResolver.ResolveReference(reference);
return Serializer.ReferenceResolver.ResolveReference(this, reference);
}
else if (string.Equals(propertyName, JsonTypeReflector.TypePropertyName, StringComparison.Ordinal))
{
@@ -598,7 +598,7 @@ namespace Newtonsoft.Json.Serialization
private object PopulateDictionary(IWrappedDictionary dictionary, JsonReader reader, JsonDictionaryContract contract, string id)
{
if (id != null)
Serializer.ReferenceResolver.AddReference(id, dictionary.UnderlyingDictionary);
Serializer.ReferenceResolver.AddReference(this, id, dictionary.UnderlyingDictionary);
contract.InvokeOnDeserializing(dictionary.UnderlyingDictionary, Serializer.Context);
@@ -686,7 +686,7 @@ namespace Newtonsoft.Json.Serialization
object list = wrappedList.UnderlyingCollection;
if (reference != null)
Serializer.ReferenceResolver.AddReference(reference, list);
Serializer.ReferenceResolver.AddReference(this, reference, list);
contract.InvokeOnDeserializing(list, Serializer.Context);
@@ -756,7 +756,7 @@ namespace Newtonsoft.Json.Serialization
object createdObject = contract.ISerializableCreator(serializationInfo, Serializer.Context);
if (id != null)
Serializer.ReferenceResolver.AddReference(id, createdObject);
Serializer.ReferenceResolver.AddReference(this, id, createdObject);
// these are together because OnDeserializing takes an object but for an ISerializable the object is full created in the constructor
contract.InvokeOnDeserializing(createdObject, Serializer.Context);
@@ -781,7 +781,7 @@ namespace Newtonsoft.Json.Serialization
throw new JsonSerializationException("Unable to find a default constructor to use for type {0}.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType));
if (id != null)
Serializer.ReferenceResolver.AddReference(id, newObject);
Serializer.ReferenceResolver.AddReference(this, id, newObject);
contract.InvokeOnDeserializing(newObject, Serializer.Context);
@@ -920,7 +920,7 @@ namespace Newtonsoft.Json.Serialization
object createdObject = constructorInfo.Invoke(constructorParameters.Values.ToArray());
if (id != null)
Serializer.ReferenceResolver.AddReference(id, createdObject);
Serializer.ReferenceResolver.AddReference(this, id, createdObject);
contract.InvokeOnDeserializing(createdObject, Serializer.Context);
@@ -976,7 +976,7 @@ namespace Newtonsoft.Json.Serialization
contract.Properties.Where(m => m.Required != Required.Default).ToDictionary(m => m, m => RequiredValue.None);
if (id != null)
Serializer.ReferenceResolver.AddReference(id, newObject);
Serializer.ReferenceResolver.AddReference(this, id, newObject);
int initialDepth = reader.Depth;
@@ -171,7 +171,7 @@ namespace Newtonsoft.Json.Serialization
if (!isReference.Value)
return false;
return Serializer.ReferenceResolver.IsReferenced(value);
return Serializer.ReferenceResolver.IsReferenced(this, value);
}
private void WriteMemberInfoProperty(JsonWriter writer, object memberValue, JsonProperty property, JsonContract contract)
@@ -231,7 +231,7 @@ namespace Newtonsoft.Json.Serialization
{
writer.WriteStartObject();
writer.WritePropertyName(JsonTypeReflector.RefPropertyName);
writer.WriteValue(Serializer.ReferenceResolver.GetReference(value));
writer.WriteValue(Serializer.ReferenceResolver.GetReference(this, value));
writer.WriteEndObject();
}
@@ -299,7 +299,7 @@ namespace Newtonsoft.Json.Serialization
if (isReference)
{
writer.WritePropertyName(JsonTypeReflector.IdPropertyName);
writer.WriteValue(Serializer.ReferenceResolver.GetReference(value));
writer.WriteValue(Serializer.ReferenceResolver.GetReference(this, value));
}
if (ShouldWriteType(TypeNameHandling.Objects, contract, member, collectionValueContract))
{
@@ -386,7 +386,7 @@ namespace Newtonsoft.Json.Serialization
if (isReference)
{
writer.WritePropertyName(JsonTypeReflector.IdPropertyName);
writer.WriteValue(Serializer.ReferenceResolver.GetReference(values.UnderlyingCollection));
writer.WriteValue(Serializer.ReferenceResolver.GetReference(this, values.UnderlyingCollection));
}
if (includeTypeDetails)
{
@@ -539,7 +539,7 @@ namespace Newtonsoft.Json.Serialization
if (isReference)
{
writer.WritePropertyName(JsonTypeReflector.IdPropertyName);
writer.WriteValue(Serializer.ReferenceResolver.GetReference(values.UnderlyingDictionary));
writer.WriteValue(Serializer.ReferenceResolver.GetReference(this, values.UnderlyingDictionary));
}
if (ShouldWriteType(TypeNameHandling.Objects, contract, member, collectionValueContract))
{
@@ -125,6 +125,14 @@ namespace Newtonsoft.Json.Serialization
set { _serializer.Context = value; }
}
internal JsonSerializerInternalBase GetInternalSerializer()
{
if (_serializerReader != null)
return _serializerReader;
else
return _serializerWriter;
}
public JsonSerializerProxy(JsonSerializerInternalReader serializerReader)
{
ValidationUtils.ArgumentNotNull(serializerReader, "serializerReader");