diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index 1b5c059e..e958273c 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -76,5 +76,5 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] #if !PocketPC -[assembly: AssemblyFileVersion("4.5.9.15313")] +[assembly: AssemblyFileVersion("4.5.9.15324")] #endif diff --git a/Src/Newtonsoft.Json.Tests/Serialization/PreserveReferencesHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/PreserveReferencesHandlingTests.cs index 5bc7fa46..beb5ea66 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/PreserveReferencesHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/PreserveReferencesHandlingTests.cs @@ -26,6 +26,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.Serialization.Formatters; using System.Text; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Tests.TestObjects; @@ -1055,6 +1056,27 @@ namespace Newtonsoft.Json.Tests.Serialization Assert.IsTrue(ReferenceEquals(c2, c3)); Assert.IsFalse(ReferenceEquals(c2, c4)); } + + [Test] + public void DuplicateId() + { + string json = @"{ + ""Data"": { + ""Prop1"": { + ""$id"": ""1"", + ""MyProperty"": 0 + }, + ""Prop2"": { + ""$id"": ""1"", + ""MyProperty"": 0 + } + } +}"; + + ExceptionAssert.Throws( + "Error reading object reference '1'. Path 'Data.Prop2.MyProperty', line 9, position 20.", + () => JsonConvert.DeserializeObject(json)); + } } public class PropertyItemIsReferenceBody diff --git a/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs b/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs index 59ac00b2..c1f02694 100644 --- a/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs +++ b/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs @@ -169,7 +169,7 @@ namespace Newtonsoft.Json.Converters .FormatWith(CultureInfo.InvariantCulture, n2, t.Name)); } - map.Add(n1, n2); + map.Set(n1, n2); } _enumMemberNamesPerType[t] = map; diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index afd5f6cd..dcb0baa9 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -90,7 +90,7 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] #if !PocketPC -[assembly: AssemblyFileVersion("4.5.9.15313")] +[assembly: AssemblyFileVersion("4.5.9.15324")] #endif [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs index 02bb1893..ceb86b80 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs @@ -63,7 +63,7 @@ namespace Newtonsoft.Json.Serialization { _referenceCount++; reference = _referenceCount.ToString(CultureInfo.InvariantCulture); - mappings.Add(reference, value); + mappings.Set(reference, value); } return reference; @@ -71,7 +71,7 @@ namespace Newtonsoft.Json.Serialization public void AddReference(object context, string reference, object value) { - GetMappings(context).Add(reference, value); + GetMappings(context).Set(reference, value); } public bool IsReferenced(object context, object value) diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs index 145ac856..dda63122 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs @@ -73,7 +73,9 @@ namespace Newtonsoft.Json.Serialization if (_mappings == null) _mappings = new BidirectionalDictionary( EqualityComparer.Default, - new ReferenceEqualsEqualityComparer()); + new ReferenceEqualsEqualityComparer(), + "A different value already has the Id '{0}'.", + "A different Id has already been assigned for value '{0}'."); return _mappings; } diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 6fc3f9d0..c753fbea 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -761,6 +761,18 @@ To fix this error either change the JSON to a {1} or change the deserialized typ return false; } + private void AddReference(JsonReader reader, string id, object value) + { + try + { + Serializer.ReferenceResolver.AddReference(this, id, value); + } + catch (Exception ex) + { + throw JsonSerializationException.Create(reader, "Error reading object reference '{0}'.".FormatWith(CultureInfo.InvariantCulture, id), ex); + } + } + private bool HasFlag(DefaultValueHandling value, DefaultValueHandling flag) { return ((value & flag) == flag); @@ -799,7 +811,7 @@ To fix this error either change the JSON to a {1} or change the deserialized typ object dictionary = wrappedDictionary.UnderlyingDictionary; if (id != null) - Serializer.ReferenceResolver.AddReference(this, id, dictionary); + AddReference(reader, id, dictionary); contract.InvokeOnDeserializing(dictionary, Serializer.Context); @@ -872,7 +884,7 @@ To fix this error either change the JSON to a {1} or change the deserialized typ int rank = contract.UnderlyingType.GetArrayRank(); if (id != null) - Serializer.ReferenceResolver.AddReference(this, id, list); + AddReference(reader, id, list); contract.InvokeOnDeserializing(list, Serializer.Context); @@ -1010,7 +1022,7 @@ To fix this error either change the JSON to a {1} or change the deserialized typ object list = wrappedList.UnderlyingCollection; if (id != null) - Serializer.ReferenceResolver.AddReference(this, id, list); + AddReference(reader, id, list); // can't populate an existing array if (wrappedList.IsFixedSize) @@ -1137,7 +1149,7 @@ To fix this error either change the environment to be fully trusted, change the object createdObject = contract.ISerializableCreator(serializationInfo, Serializer.Context); if (id != null) - Serializer.ReferenceResolver.AddReference(this, id, createdObject); + AddReference(reader, id, createdObject); // these are together because OnDeserializing takes an object but for an ISerializable the object is fully created in the constructor contract.InvokeOnDeserializing(createdObject, Serializer.Context); @@ -1162,7 +1174,7 @@ To fix this error either change the environment to be fully trusted, change the throw JsonSerializationException.Create(reader, "Unable to find a default constructor to use for type {0}.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType)); if (id != null) - Serializer.ReferenceResolver.AddReference(this, id, newObject); + AddReference(reader, id, newObject); contract.InvokeOnDeserializing(newObject, Serializer.Context); @@ -1257,7 +1269,7 @@ To fix this error either change the environment to be fully trusted, change the object createdObject = constructorInfo.Invoke(constructorParameters.Values.ToArray()); if (id != null) - Serializer.ReferenceResolver.AddReference(this, id, createdObject); + AddReference(reader, id, createdObject); contract.InvokeOnDeserializing(createdObject, Serializer.Context); @@ -1478,7 +1490,7 @@ To fix this error either change the environment to be fully trusted, change the : null; if (id != null) - Serializer.ReferenceResolver.AddReference(this, id, newObject); + AddReference(reader, id, newObject); int initialDepth = reader.Depth; diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index c1a4bdbf..25244b37 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -254,10 +254,23 @@ namespace Newtonsoft.Json.Serialization { writer.WriteStartObject(); writer.WritePropertyName(JsonTypeReflector.RefPropertyName); - writer.WriteValue(Serializer.ReferenceResolver.GetReference(this, value)); + writer.WriteValue(GetReference(writer, value)); writer.WriteEndObject(); } + private string GetReference(JsonWriter writer, object value) + { + try + { + string reference = Serializer.ReferenceResolver.GetReference(this, value); + return reference; + } + catch (Exception ex) + { + throw JsonSerializationException.Create(null, writer.ContainerPath, "Error writing object reference for '{0}'.".FormatWith(CultureInfo.InvariantCulture, value.GetType()), ex); + } + } + internal static bool TryConvertToString(object value, Type type, out string s) { #if !(PocketPC || NETFX_CORE || PORTABLE) @@ -397,7 +410,7 @@ namespace Newtonsoft.Json.Serialization if (isReference) { writer.WritePropertyName(JsonTypeReflector.IdPropertyName); - writer.WriteValue(Serializer.ReferenceResolver.GetReference(this, value)); + writer.WriteValue(GetReference(writer, value)); } if (ShouldWriteType(TypeNameHandling.Objects, contract, member, collectionContract, containerProperty)) { @@ -584,7 +597,7 @@ namespace Newtonsoft.Json.Serialization if (isReference) { writer.WritePropertyName(JsonTypeReflector.IdPropertyName); - writer.WriteValue(Serializer.ReferenceResolver.GetReference(this, values)); + writer.WriteValue(GetReference(writer, values)); } if (includeTypeDetails) { diff --git a/Src/Newtonsoft.Json/Utilities/BidirectionalDictionary.cs b/Src/Newtonsoft.Json/Utilities/BidirectionalDictionary.cs index 19c8a985..37ecaa4b 100644 --- a/Src/Newtonsoft.Json/Utilities/BidirectionalDictionary.cs +++ b/Src/Newtonsoft.Json/Utilities/BidirectionalDictionary.cs @@ -25,6 +25,7 @@ using System; using System.Collections.Generic; +using System.Globalization; namespace Newtonsoft.Json.Utilities { @@ -32,6 +33,8 @@ namespace Newtonsoft.Json.Utilities { private readonly IDictionary _firstToSecond; private readonly IDictionary _secondToFirst; + private readonly string _duplicateFirstErrorMessage; + private readonly string _duplicateSecondErrorMessage; public BidirectionalDictionary() : this(EqualityComparer.Default, EqualityComparer.Default) @@ -39,17 +42,40 @@ namespace Newtonsoft.Json.Utilities } public BidirectionalDictionary(IEqualityComparer firstEqualityComparer, IEqualityComparer secondEqualityComparer) + : this( + firstEqualityComparer, + secondEqualityComparer, + "Duplicate item already exists for '{0}'.", + "Duplicate item already exists for '{0}'.") + { + } + + public BidirectionalDictionary(IEqualityComparer firstEqualityComparer, IEqualityComparer secondEqualityComparer, + string duplicateFirstErrorMessage, string duplicateSecondErrorMessage) { _firstToSecond = new Dictionary(firstEqualityComparer); _secondToFirst = new Dictionary(secondEqualityComparer); + _duplicateFirstErrorMessage = duplicateFirstErrorMessage; + _duplicateSecondErrorMessage = duplicateSecondErrorMessage; } - public void Add(TFirst first, TSecond second) + public void Set(TFirst first, TSecond second) { - if (_firstToSecond.ContainsKey(first) || _secondToFirst.ContainsKey(second)) + TFirst existingFirst; + TSecond existingSecond; + + if (_firstToSecond.TryGetValue(first, out existingSecond)) { - throw new ArgumentException("Duplicate first or second"); + if (!existingSecond.Equals(second)) + throw new ArgumentException(_duplicateFirstErrorMessage.FormatWith(CultureInfo.InvariantCulture, first)); } + + if (_secondToFirst.TryGetValue(second, out existingFirst)) + { + if (!existingFirst.Equals(first)) + throw new ArgumentException(_duplicateSecondErrorMessage.FormatWith(CultureInfo.InvariantCulture, second)); + } + _firstToSecond.Add(first, second); _secondToFirst.Add(second, first); }