Skip to content

Commit

Permalink
[FIXED]: Since the Issue JamesNK#401(JamesNK#401) was closed because …
Browse files Browse the repository at this point in the history
…changing the current bahavior unconditionally would cause a breaking change, this change set add the ability to override the behavior by providing a new setting when configuring the serializer. All the configuration paths are supported(JsonSerializerSettings, Json*Attribute, JsonContract, JsonProperty, etc...).

See the ReferenceComparisonHandling enum.
[ADDED]: ReferenceComparisonHandlingTests
  • Loading branch information
luca-piombino-deltatre committed May 16, 2015
1 parent d53ac4c commit 7c9ea84
Show file tree
Hide file tree
Showing 13 changed files with 370 additions and 39 deletions.
1 change: 1 addition & 0 deletions Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@
<Compile Include="Serialization\MissingMemberHandlingTests.cs" />
<Compile Include="Serialization\NullValueHandlingTests.cs" />
<Compile Include="Serialization\PopulateTests.cs" />
<Compile Include="Serialization\ReferenceComparisonHandlingTests.cs" />
<Compile Include="Serialization\PreserveReferencesHandlingTests.cs" />
<Compile Include="Serialization\ReferenceLoopHandlingTests.cs" />
<Compile Include="Serialization\ReflectionAttributeProviderTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1151,17 +1151,6 @@ public void DuplicateId()
MetadataPropertyHandling = MetadataPropertyHandling.Default
}), "Error reading object reference '1'. Path 'Data.Prop2.MyProperty', line 9, position 20.");
}

[Test]
public void ShouldCheckForCircularReferencesUsingReferenceEquality()
{
var source = new ValueContainer
{
Value = new object()
};

string json = JsonConvert.SerializeObject(source, Formatting.Indented);
}
}

public class PropertyItemIsReferenceBody
Expand Down Expand Up @@ -1205,27 +1194,4 @@ public class ReferenceObject
public string String { get; set; }
public int Integer { get; set; }
}

public class ValueContainer
{
public object Value;

public override bool Equals(object obj)
{
if (obj == null)
return false;

var otherContainer = obj as ValueContainer;

if (otherContainer != null)
return EqualityComparer<object>.Default.Equals(Value, otherContainer.Value);

return EqualityComparer<object>.Default.Equals(Value, obj);
}

public override int GetHashCode()
{
return EqualityComparer<object>.Default.GetHashCode(Value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

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;
#if NETFX_CORE
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute;
using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute;
#elif ASPNETCORE50
using Xunit;
using Test = Xunit.FactAttribute;
using Assert = Newtonsoft.Json.Tests.XUnitAssert;
#else
using NUnit.Framework;
#endif

namespace Newtonsoft.Json.Tests.Serialization
{
[TestFixture]
public class ReferenceComparisonHandlingTests : TestFixtureBase
{
[Test]
public void ShouldUseObjectEqualsByDefault()
{
bool equalsCalled = false;
var source = new SettingNotSpecified(() => equalsCalled = true)
{
Value = new object()
};

Assert.Throws<JsonSerializationException>(delegate
{
JsonConvert.SerializeObject(source, Formatting.Indented);
});
Assert.IsTrue(equalsCalled);
}

[Test]
public void ShouldUseTheJsonSerializerSetting()
{
bool equalsCalled = false;
var source = new SettingNotSpecified(() => equalsCalled = true)
{
Value = new object()
};

JsonConvert.SerializeObject(source, new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ReferenceComparisonHandling = ReferenceComparisonHandling.ReferenceEquals
});

Assert.IsFalse(equalsCalled);
}

[Test]
public void ShouldUseTheJsonPropertyAttributeSetting()
{
bool equalsCalled = false;
var source = new SettingSpecifiedAtThePropertyLevel(() => equalsCalled = true)
{
Value = new object()
};

JsonConvert.SerializeObject(source, Formatting.Indented);

Assert.IsFalse(equalsCalled);
}

[Test]
public void ShouldUseTheJsonObjectAttributeSetting()
{
bool equalsCalled = false;
var source = new SettingSpecifiedAtTheClassLevel(() => equalsCalled = true)
{
Value = new object()
};

JsonConvert.SerializeObject(source, Formatting.Indented);

Assert.IsFalse(equalsCalled);
}

[Test]
public void ShouldUseTheJsonPropertyAttributeSettingsForTheContaintedItems()
{
bool equalsCalled = false;
var source = new SettingSpecifiedAtThePropertyLevelForItemContainedInTheProperty
{
Value = new SettingSpecifiedAtTheClassLevel(() => equalsCalled = true)
{
Value = new object()
}
};

JsonConvert.SerializeObject(source, Formatting.Indented);

Assert.IsFalse(equalsCalled);
}
}

public class SettingNotSpecified
{
public virtual object Value { get; set; }

public SettingNotSpecified(Action onEquals = null)
{
_onEquals = onEquals;
}

public override bool Equals(object obj)
{
if(_onEquals != null)_onEquals();

if (obj == null)
return false;

var otherContainer = obj as SettingNotSpecified;

if (otherContainer != null)
return EqualityComparer<object>.Default.Equals(Value, otherContainer.Value);

return EqualityComparer<object>.Default.Equals(Value, obj);
}

public override int GetHashCode()
{
return EqualityComparer<object>.Default.GetHashCode(Value);
}

[JsonIgnore]
internal Action _onEquals;
}

[JsonObject(ItemReferenceComparisonHandling = ReferenceComparisonHandling.ReferenceEquals)]
public class SettingSpecifiedAtTheClassLevel : SettingNotSpecified
{
public SettingSpecifiedAtTheClassLevel(Action onEquals = null)
: base(onEquals)
{

}
}

public class SettingSpecifiedAtThePropertyLevel : SettingNotSpecified
{
[JsonProperty(ReferenceComparisonHandling = ReferenceComparisonHandling.ReferenceEquals)]
public override object Value { get; set; }

public SettingSpecifiedAtThePropertyLevel(Action onEquals = null)
: base(onEquals)
{
}
}

public class SettingSpecifiedAtThePropertyLevelForItemContainedInTheProperty
{
[JsonProperty(ItemReferenceComparisonHandling = ReferenceComparisonHandling.ReferenceEquals)]
public SettingNotSpecified Value;
}
}
7 changes: 7 additions & 0 deletions Src/Newtonsoft.Json/JsonContainerAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public abstract class JsonContainerAttribute : Attribute
internal bool? _isReference;
internal bool? _itemIsReference;
internal ReferenceLoopHandling? _itemReferenceLoopHandling;
internal ReferenceComparisonHandling? _itemReferenceComparisonHandling;
internal TypeNameHandling? _itemTypeNameHandling;

/// <summary>
Expand Down Expand Up @@ -109,6 +110,12 @@ public ReferenceLoopHandling ItemReferenceLoopHandling
set { _itemReferenceLoopHandling = value; }
}

public ReferenceComparisonHandling ItemReferenceComparisonHandling
{
get { return _itemReferenceComparisonHandling ?? default(ReferenceComparisonHandling); }
set { _itemReferenceComparisonHandling = value; }
}

/// <summary>
/// Gets or sets the type name handling used when serializing the collection's items.
/// </summary>
Expand Down
26 changes: 26 additions & 0 deletions Src/Newtonsoft.Json/JsonPropertyAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public sealed class JsonPropertyAttribute : Attribute
internal bool? _itemIsReference;
internal ReferenceLoopHandling? _itemReferenceLoopHandling;
internal TypeNameHandling? _itemTypeNameHandling;
internal ReferenceComparisonHandling? _referenceComparisonHandling;
internal ReferenceComparisonHandling? _itemReferenceComparisonHandling;

/// <summary>
/// Gets or sets the converter used when serializing the property's collection items.
Expand Down Expand Up @@ -94,6 +96,18 @@ public ReferenceLoopHandling ReferenceLoopHandling
set { _referenceLoopHandling = value; }
}

/// <summary>
/// Gets or sets the method used to compare object references when serializing this property.
/// </summary>
/// <value>
/// The reference compare handling.
/// </value>
public ReferenceComparisonHandling ReferenceComparisonHandling
{
get { return _referenceComparisonHandling ?? default(ReferenceComparisonHandling); }
set { _referenceComparisonHandling = value; }
}

/// <summary>
/// Gets or sets the object creation handling used when deserializing this property.
/// </summary>
Expand Down Expand Up @@ -162,6 +176,18 @@ public ReferenceLoopHandling ItemReferenceLoopHandling
set { _itemReferenceLoopHandling = value; }
}

/// <summary>
/// Gets or sets the method used to compare object references when serializing the property's collection items.
/// </summary>
/// <value>
/// The item reference compare handling.
/// </value>
public ReferenceComparisonHandling ItemReferenceComparisonHandling
{
get { return _itemReferenceComparisonHandling ?? default(ReferenceComparisonHandling); }
set { _itemReferenceComparisonHandling = value; }
}

/// <summary>
/// Gets or sets the the type name handling used when serializing the property's collection items.
/// </summary>
Expand Down
27 changes: 27 additions & 0 deletions Src/Newtonsoft.Json/JsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class JsonSerializer
internal FormatterAssemblyStyle _typeNameAssemblyFormat;
internal PreserveReferencesHandling _preserveReferencesHandling;
internal ReferenceLoopHandling _referenceLoopHandling;
internal ReferenceComparisonHandling _referenceComparisonHandling;
internal MissingMemberHandling _missingMemberHandling;
internal ObjectCreationHandling _objectCreationHandling;
internal NullValueHandling _nullValueHandling;
Expand Down Expand Up @@ -180,6 +181,28 @@ public virtual ReferenceLoopHandling ReferenceLoopHandling
}
}

/// <summary>
/// Gets or sets how object are compared for equality when detecting circular references.
/// </summary>
/// <value>
/// The reference comparison handling.
/// </value>
/// <exception cref="System.ArgumentOutOfRangeException">value</exception>
public ReferenceComparisonHandling ReferenceComparisonHandling
{
get { return _referenceComparisonHandling; }
set
{
if (value < ReferenceComparisonHandling.ObjectEquals || value > ReferenceComparisonHandling.ReferenceEquals)
throw new ArgumentOutOfRangeException("value");

_referenceComparisonHandling = value;
}
}




/// <summary>
/// Get or set how missing members (e.g. JSON contains a property that isn't a member on the object) are handled during deserialization.
/// </summary>
Expand Down Expand Up @@ -539,6 +562,10 @@ private static void ApplySerializerSettings(JsonSerializer serializer, JsonSeria
serializer.PreserveReferencesHandling = settings.PreserveReferencesHandling;
if (settings._referenceLoopHandling != null)
serializer.ReferenceLoopHandling = settings.ReferenceLoopHandling;

if (settings.ReferenceComparisonHandling != null)
serializer.ReferenceComparisonHandling = settings.ReferenceComparisonHandling;

if (settings._missingMemberHandling != null)
serializer.MissingMemberHandling = settings.MissingMemberHandling;
if (settings._objectCreationHandling != null)
Expand Down
7 changes: 7 additions & 0 deletions Src/Newtonsoft.Json/JsonSerializerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public class JsonSerializerSettings
internal ConstructorHandling? _constructorHandling;
internal TypeNameHandling? _typeNameHandling;
internal MetadataPropertyHandling? _metadataPropertyHandling;
internal ReferenceComparisonHandling? _referenceComparisonHandling;

/// <summary>
/// Gets or sets how reference loops (e.g. a class referencing itself) is handled.
Expand All @@ -96,6 +97,12 @@ public ReferenceLoopHandling ReferenceLoopHandling
set { _referenceLoopHandling = value; }
}

public ReferenceComparisonHandling ReferenceComparisonHandling
{
get { return _referenceComparisonHandling ?? default(ReferenceComparisonHandling); }
set { _referenceComparisonHandling = value; }
}

/// <summary>
/// Gets or sets how missing members (e.g. JSON contains a property that isn't a member on the object) are handled during deserialization.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions Src/Newtonsoft.Json/Newtonsoft.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
<Compile Include="ObjectCreationHandling.cs" />
<Compile Include="PreserveReferencesHandling.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ReferenceComparisonHandling.cs" />
<Compile Include="ReferenceLoopHandling.cs" />
<Compile Include="Required.cs" />
<Compile Include="Schema\Extensions.cs" />
Expand Down
Loading

0 comments on commit 7c9ea84

Please sign in to comment.