diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index bead7fb8..d89b7ace 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -798,6 +798,9 @@ namespace Newtonsoft.Json.Tests.Serialization serializer.Culture = new CultureInfo("en-nz"); Assert.AreEqual("en-NZ", serializer.Culture.ToString()); + serializer.EqualityComparer = EqualityComparer.Default; + Assert.AreEqual(EqualityComparer.Default, serializer.EqualityComparer); + serializer.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat; Assert.AreEqual(DateFormatHandling.MicrosoftDateFormat, serializer.DateFormatHandling); @@ -890,6 +893,9 @@ namespace Newtonsoft.Json.Tests.Serialization settings.Culture = new CultureInfo("en-nz"); Assert.AreEqual("en-NZ", settings.Culture.ToString()); + settings.EqualityComparer = EqualityComparer.Default; + Assert.AreEqual(EqualityComparer.Default, settings.EqualityComparer); + settings.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat; Assert.AreEqual(DateFormatHandling.MicrosoftDateFormat, settings.DateFormatHandling); @@ -988,6 +994,9 @@ namespace Newtonsoft.Json.Tests.Serialization serializerProxy.Culture = new CultureInfo("en-nz"); Assert.AreEqual("en-NZ", serializerProxy.Culture.ToString()); + serializerProxy.EqualityComparer = EqualityComparer.Default; + Assert.AreEqual(EqualityComparer.Default, serializerProxy.EqualityComparer); + serializerProxy.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat; Assert.AreEqual(DateFormatHandling.MicrosoftDateFormat, serializerProxy.DateFormatHandling); diff --git a/Src/Newtonsoft.Json.Tests/Serialization/ReferenceLoopHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/ReferenceLoopHandlingTests.cs index af2bbb62..27adf4e6 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/ReferenceLoopHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/ReferenceLoopHandlingTests.cs @@ -24,7 +24,9 @@ #endregion using System; +using System.Collections; using System.Collections.Generic; +using System.Runtime.CompilerServices; #if NETFX_CORE using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute; @@ -268,6 +270,80 @@ namespace Newtonsoft.Json.Tests.Serialization Assert.AreEqual(@"{""child"":{""name"":""child""},""name"":""parent""}", c); } #endif + + [Test] + public void EqualityComparer() + { + AccountWithEquals account = new AccountWithEquals + { + Name = "main" + }; + AccountWithEquals manager = new AccountWithEquals + { + Name = "main" + }; + account.Manager = manager; + + ExceptionAssert.Throws( + () => JsonConvert.SerializeObject(account), + "Self referencing loop detected for property 'Manager' with type 'Newtonsoft.Json.Tests.Serialization.AccountWithEquals'. Path ''."); + + string json = JsonConvert.SerializeObject(account, new JsonSerializerSettings + { + EqualityComparer = new ReferenceEqualsEqualityComparer(), + Formatting = Formatting.Indented + }); + + StringAssert.AreEqual(@"{ + ""Name"": ""main"", + ""Manager"": { + ""Name"": ""main"", + ""Manager"": null + } +}", json); + } + } + + public class ReferenceEqualsEqualityComparer : IEqualityComparer + { + bool IEqualityComparer.Equals(object x, object y) + { + return ReferenceEquals(x, y); + } + + int IEqualityComparer.GetHashCode(object obj) + { +#if !(NETFX_CORE) + // 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 + } + } + + public class AccountWithEquals + { + public string Name { get; set; } + public AccountWithEquals Manager { get; set; } + + public override bool Equals(object obj) + { + AccountWithEquals a = obj as AccountWithEquals; + if (a == null) + return false; + + return Name == a.Name; + } + + public override int GetHashCode() + { + if (Name == null) + return 0; + + return Name.GetHashCode(); + } } public class PropertyItemReferenceLoopHandling diff --git a/Src/Newtonsoft.Json/JsonSerializer.cs b/Src/Newtonsoft.Json/JsonSerializer.cs index f7231a7c..30d5193d 100644 --- a/Src/Newtonsoft.Json/JsonSerializer.cs +++ b/Src/Newtonsoft.Json/JsonSerializer.cs @@ -24,6 +24,7 @@ #endregion using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -56,6 +57,7 @@ namespace Newtonsoft.Json internal JsonConverterCollection _converters; internal IContractResolver _contractResolver; internal ITraceWriter _traceWriter; + internal IEqualityComparer _equalityComparer; internal SerializationBinder _binder; internal StreamingContext _context; private IReferenceResolver _referenceResolver; @@ -119,6 +121,16 @@ namespace Newtonsoft.Json set { _traceWriter = value; } } + /// + /// Gets or sets the equality comparer used by the serializer when comparing references. + /// + /// The equality comparer. + public virtual IEqualityComparer EqualityComparer + { + get { return _equalityComparer; } + set { _equalityComparer = value; } + } + /// /// Gets or sets how type name writing and reading is handled by the serializer. /// @@ -563,6 +575,8 @@ namespace Newtonsoft.Json serializer.ReferenceResolver = settings.ReferenceResolverProvider(); if (settings.TraceWriter != null) serializer.TraceWriter = settings.TraceWriter; + if (settings.EqualityComparer != null) + serializer.EqualityComparer = settings.EqualityComparer; if (settings.Binder != null) serializer.Binder = settings.Binder; diff --git a/Src/Newtonsoft.Json/JsonSerializerSettings.cs b/Src/Newtonsoft.Json/JsonSerializerSettings.cs index 903c8e25..96587c77 100644 --- a/Src/Newtonsoft.Json/JsonSerializerSettings.cs +++ b/Src/Newtonsoft.Json/JsonSerializerSettings.cs @@ -24,6 +24,7 @@ #endregion using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Runtime.Serialization.Formatters; @@ -199,6 +200,12 @@ namespace Newtonsoft.Json /// The contract resolver. public IContractResolver ContractResolver { get; set; } + /// + /// Gets or sets the equality comparer used by the serializer when comparing references. + /// + /// The equality comparer. + public IEqualityComparer EqualityComparer { get; set; } + /// /// Gets or sets the used by the serializer when resolving references. /// diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs index b49b662d..5f2c0723 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs @@ -47,8 +47,8 @@ namespace Newtonsoft.Json.Serialization // 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; + // put all objects in the same bucket so ReferenceEquals is called on all + return -1; #endif } } diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index f3f9f6ae..960fd72e 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -272,7 +272,11 @@ namespace Newtonsoft.Json.Serialization if (referenceLoopHandling == null && containerContract != null) referenceLoopHandling = containerContract.ItemReferenceLoopHandling; - if (_serializeStack.IndexOf(value) != -1) + bool exists = (Serializer._equalityComparer != null) + ? _serializeStack.Contains(value, Serializer._equalityComparer) + : _serializeStack.Contains(value); + + if (exists) { string message = "Self referencing loop detected"; if (property != null) diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs index a3f3a322..65d4e69e 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs @@ -24,6 +24,7 @@ #endregion using System; +using System.Collections; using System.Globalization; using System.Runtime.Serialization.Formatters; using Newtonsoft.Json.Utilities; @@ -55,6 +56,12 @@ namespace Newtonsoft.Json.Serialization set { _serializer.TraceWriter = value; } } + public override IEqualityComparer EqualityComparer + { + get { return _serializer.EqualityComparer; } + set { _serializer.EqualityComparer = value; } + } + public override JsonConverterCollection Converters { get { return _serializer.Converters; } diff --git a/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs b/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs index 92361733..f82c098d 100644 --- a/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs @@ -190,8 +190,20 @@ namespace Newtonsoft.Json.Utilities return -1; } + public static bool Contains(this IEnumerable list, object value, IEqualityComparer comparer) + { + foreach (object item in list) + { + if (comparer.Equals(item, value)) + { + return true; + } + } + return false; + } + /// - /// Returns the index of the first occurrence in a sequence by using a specified IEqualityComparer. + /// Returns the index of the first occurrence in a sequence by using a specified IEqualityComparer{TSource}. /// /// The type of the elements of source. /// A sequence in which to locate a value.