diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 172bfbb8..3042e735 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -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); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Serialization/PreserveReferencesHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/PreserveReferencesHandlingTests.cs index 0d5b78c7..cb72ffbd 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/PreserveReferencesHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/PreserveReferencesHandlingTests.cs @@ -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(json, serializerSettings); Assert.IsNotNull(deserializedUser); } + + [Test] + public void PreserveReferencesHandlingWithReusedJsonSerializer() + { + MyClass c = new MyClass(); + + IList myClasses1 = new List + { + 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 myClasses2; + + using (var sr = new StreamReader(ms)) + using (var reader = new JsonTextReader(sr)) + { + myClasses2 = ser.Deserialize>(reader); + } + + Assert.AreEqual(2, myClasses2.Count); + Assert.AreEqual(myClasses2[0], myClasses2[1]); + + Assert.AreNotEqual(myClasses1[0], myClasses2[0]); + } } } diff --git a/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs index cd226ff7..b1de3f72 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs @@ -35,72 +35,53 @@ namespace Newtonsoft.Json.Serialization { internal class DefaultReferenceResolver : IReferenceResolver { - private class ReferenceEqualsEqualityComparer : IEqualityComparer - { - bool IEqualityComparer.Equals(object x, object y) - { - return ReferenceEquals(x, y); - } - - int IEqualityComparer.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 _mappings; - private BidirectionalDictionary Mappings + private BidirectionalDictionary 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( - EqualityComparer.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); } } -} +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/IReferenceResolver.cs b/Src/Newtonsoft.Json/Serialization/IReferenceResolver.cs index c482de25..bcfd1720 100644 --- a/Src/Newtonsoft.Json/Serialization/IReferenceResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/IReferenceResolver.cs @@ -33,28 +33,32 @@ namespace Newtonsoft.Json.Serialization /// /// Resolves a reference to its object. /// + /// The serialization context. /// The reference to resolve. /// The object that - object ResolveReference(string reference); + object ResolveReference(object context, string reference); /// /// Gets the reference for the sepecified object. /// + /// The serialization context. /// The object to get a reference for. /// The reference to the object. - string GetReference(object value); + string GetReference(object context, object value); /// /// Determines whether the specified object is referenced. /// + /// The serialization context. /// The object to test for a reference. /// /// true if the specified object is referenced; otherwise, false. /// - bool IsReferenced(object value); + bool IsReferenced(object context, object value); /// /// Adds a reference to the specified object. /// + /// The serialization context. /// The reference. /// The object to reference. - void AddReference(string reference, object value); + void AddReference(object context, string reference, object value); } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs index 2a61f533..f455636f 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs @@ -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 + { + bool IEqualityComparer.Equals(object x, object y) + { + return ReferenceEquals(x, y); + } + + int IEqualityComparer.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 _mappings; internal JsonSerializer Serializer { get; private set; } @@ -42,6 +64,21 @@ namespace Newtonsoft.Json.Serialization Serializer = serializer; } + internal BidirectionalDictionary 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( + EqualityComparer.Default, + new ReferenceEqualsEqualityComparer()); + + return _mappings; + } + } + protected ErrorContext GetErrorContext(object currentObject, object member, Exception error) { if (_currentErrorContext == null) diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 044a17e7..c7d56540 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -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; diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index 9e06fc6c..c25e995e 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -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)) { diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs index 44636821..d73090cd 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs @@ -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");