Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement case sensitive property matching #2709

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8196,6 +8196,9 @@ public void ShallowCopy_CopyAllProperties(bool specifyDateFormatString)
Assert.AreEqual(settings.CheckAdditionalContent, clone.CheckAdditionalContent);
Assert.IsTrue(propertyNames.Remove(nameof(JsonSerializerSettings.CheckAdditionalContent)));

Assert.AreEqual(settings.PropertyCaseSensitivityHandling, clone.PropertyCaseSensitivityHandling);
Assert.IsTrue(propertyNames.Remove(nameof(JsonSerializerSettings.PropertyCaseSensitivityHandling)));

Assert.AreEqual(0, propertyNames.Count);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#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.ComponentModel;
using System.Reflection;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Tests.TestObjects;
#if DNXCORE50
using Xunit;
using Test = Xunit.FactAttribute;
using Assert = Newtonsoft.Json.Tests.XUnitAssert;
#else
using NUnit.Framework;
#endif
#if NET20
using Newtonsoft.Json.Utilities.LinqBridge;
#else
using System.Linq;
#endif

namespace Newtonsoft.Json.Tests.Serialization
{
[TestFixture]
public class CaseSensitivityHandlingTests : TestFixtureBase
{
private class CaseSensitivityTestObject
{
public string Foo { get; set; }
}

[Test]
public void MemberNameMatchHandlingTest()
{
// Test default case sensitivity handling (insensitive)
var deserializeObject = JsonConvert.DeserializeObject<CaseSensitivityTestObject>(@"{""Foo"": ""bar""}",
new JsonSerializerSettings());
Assert.AreEqual("bar", deserializeObject.Foo);

// Test explicit case insensitive handling
deserializeObject = JsonConvert.DeserializeObject<CaseSensitivityTestObject>(@"{""Foo"": ""bar""}",
new JsonSerializerSettings {PropertyCaseSensitivityHandling = PropertyCaseSensitivityHandling.CaseInsensitive});
Assert.AreEqual("bar", deserializeObject.Foo);

// Test explicit case sensitive handling
deserializeObject = JsonConvert.DeserializeObject<CaseSensitivityTestObject>(@"{""foo"": ""bar""}",
new JsonSerializerSettings {PropertyCaseSensitivityHandling = PropertyCaseSensitivityHandling.CaseSensitive});
Assert.AreEqual(null, deserializeObject.Foo);
}
}
}
17 changes: 17 additions & 0 deletions Src/Newtonsoft.Json/JsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public class JsonSerializer
private FloatFormatHandling? _floatFormatHandling;
private FloatParseHandling? _floatParseHandling;
private StringEscapeHandling? _stringEscapeHandling;
private PropertyCaseSensitivityHandling? _caseSensitivityHandling;
private CultureInfo _culture;
private int? _maxDepth;
private bool _maxDepthSet;
Expand Down Expand Up @@ -485,6 +486,17 @@ public virtual StringEscapeHandling StringEscapeHandling
get => _stringEscapeHandling ?? JsonSerializerSettings.DefaultStringEscapeHandling;
set => _stringEscapeHandling = value;
}

/// <summary>
/// Gets or sets the case sensitivity settings used
/// when deserializing JSON property names.
/// The default value is <see cref="Json.PropertyCaseSensitivityHandling.CaseInsensitive" />.
/// </summary>
public virtual PropertyCaseSensitivityHandling PropertyCaseSensitivityHandling
{
get => _caseSensitivityHandling ?? JsonSerializerSettings.DefaultCaseSensitivityHandling;
set => _caseSensitivityHandling = value;
}

/// <summary>
/// Gets or sets how <see cref="DateTime"/> and <see cref="DateTimeOffset"/> values are formatted when writing JSON text,
Expand Down Expand Up @@ -563,6 +575,7 @@ public JsonSerializer()
_constructorHandling = JsonSerializerSettings.DefaultConstructorHandling;
_typeNameHandling = JsonSerializerSettings.DefaultTypeNameHandling;
_metadataPropertyHandling = JsonSerializerSettings.DefaultMetadataPropertyHandling;
_caseSensitivityHandling = JsonSerializerSettings.DefaultCaseSensitivityHandling;
_context = JsonSerializerSettings.DefaultContext;
_serializationBinder = DefaultSerializationBinder.Instance;

Expand Down Expand Up @@ -771,6 +784,10 @@ private static void ApplySerializerSettings(JsonSerializer serializer, JsonSeria
{
serializer._stringEscapeHandling = settings._stringEscapeHandling;
}
if (settings._caseSensitivityHandling != null)
{
serializer._caseSensitivityHandling = settings._caseSensitivityHandling;
}
if (settings._culture != null)
{
serializer._culture = settings._culture;
Expand Down
14 changes: 14 additions & 0 deletions Src/Newtonsoft.Json/JsonSerializerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public class JsonSerializerSettings
internal const TypeNameHandling DefaultTypeNameHandling = TypeNameHandling.None;
internal const MetadataPropertyHandling DefaultMetadataPropertyHandling = MetadataPropertyHandling.Default;
internal static readonly StreamingContext DefaultContext;
internal const PropertyCaseSensitivityHandling DefaultCaseSensitivityHandling = PropertyCaseSensitivityHandling.CaseInsensitive;

internal const Formatting DefaultFormatting = Formatting.None;
internal const DateFormatHandling DefaultDateFormatHandling = DateFormatHandling.IsoDateFormat;
Expand Down Expand Up @@ -93,6 +94,7 @@ public class JsonSerializerSettings
internal ConstructorHandling? _constructorHandling;
internal TypeNameHandling? _typeNameHandling;
internal MetadataPropertyHandling? _metadataPropertyHandling;
internal PropertyCaseSensitivityHandling? _caseSensitivityHandling;

/// <summary>
/// Gets or sets how reference loops (e.g. a class referencing itself) are handled.
Expand Down Expand Up @@ -421,6 +423,17 @@ public StringEscapeHandling StringEscapeHandling
set => _stringEscapeHandling = value;
}

/// <summary>
/// Gets or sets the case sensitivity settings used
/// when deserializing JSON property names.
/// The default value is <see cref="Json.PropertyCaseSensitivityHandling.CaseInsensitive" />.
/// </summary>
public PropertyCaseSensitivityHandling PropertyCaseSensitivityHandling
{
get => _caseSensitivityHandling ?? DefaultCaseSensitivityHandling;
set => _caseSensitivityHandling = value;
}

/// <summary>
/// Gets or sets the culture used when reading JSON.
/// The default value is <see cref="CultureInfo.InvariantCulture"/>.
Expand Down Expand Up @@ -496,6 +509,7 @@ public JsonSerializerSettings(JsonSerializerSettings original)
_referenceLoopHandling = original._referenceLoopHandling;
_checkAdditionalContent = original._checkAdditionalContent;
_stringEscapeHandling = original._stringEscapeHandling;
_caseSensitivityHandling = original._caseSensitivityHandling;
}
}
}
17 changes: 17 additions & 0 deletions Src/Newtonsoft.Json/PropertyCaseSensitivityHandling.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Newtonsoft.Json
{
/// <summary>
/// Specifies the case sensitivity setting that JSON deserialization should follow, when matching property names.
/// </summary>
public enum PropertyCaseSensitivityHandling
{
/// <summary>
/// JSON deserialization will match property names using case-sensitive comparisons.
/// </summary>
CaseSensitive = 0,
/// <summary>
/// JSON deserialization will match property names using case-insensitive comparisons.
/// </summary>
CaseInsensitive = 1
}
}
21 changes: 18 additions & 3 deletions Src/Newtonsoft.Json/Serialization/JsonPropertyCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,30 @@ public void AddProperty(JsonProperty property)

/// <summary>
/// Gets the closest matching <see cref="JsonProperty"/> object.
/// First attempts to get an exact case match of <paramref name="propertyName"/> and then
/// a case insensitive match.
/// First attempts to get an exact case match of <paramref name="propertyName"/>
/// and then a case insensitive match.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
/// <returns>A matching property if found.</returns>
public JsonProperty? GetClosestMatchProperty(string propertyName)
{
return GetClosestMatchProperty(propertyName, PropertyCaseSensitivityHandling.CaseInsensitive);
}

/// <summary>
/// Gets the closest matching <see cref="JsonProperty"/> object.
/// First attempts to get an exact case match of <paramref name="propertyName"/>
/// and then a case insensitive match, if case insensitive matching is set.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
/// <param name="casing">Case sensitivity setting to use when matching names.</param>
/// <returns>A matching property if found.</returns>
public JsonProperty? GetClosestMatchProperty(string propertyName, PropertyCaseSensitivityHandling casing)
{
JsonProperty? property = GetProperty(propertyName, StringComparison.Ordinal);
if (property == null)

// Handle case insensitive matching if allowed.
if (property == null && casing == PropertyCaseSensitivityHandling.CaseInsensitive)
{
property = GetProperty(propertyName, StringComparison.OrdinalIgnoreCase);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1865,7 +1865,7 @@ private object CreateDynamic(JsonReader reader, JsonDynamicContract contract, Js
}

// first attempt to find a settable property, otherwise fall back to a dynamic set without type
JsonProperty? property = contract.Properties.GetClosestMatchProperty(memberName);
JsonProperty? property = contract.Properties.GetClosestMatchProperty(memberName, Serializer.PropertyCaseSensitivityHandling);

if (property != null && property.Writable && !property.Ignored)
{
Expand Down Expand Up @@ -2211,8 +2211,8 @@ private List<CreatorPropertyContext> ResolvePropertyAndCreatorValues(JsonObjectC

CreatorPropertyContext creatorPropertyContext = new CreatorPropertyContext(memberName)
{
ConstructorProperty = contract.CreatorParameters.GetClosestMatchProperty(memberName),
Property = contract.Properties.GetClosestMatchProperty(memberName)
ConstructorProperty = contract.CreatorParameters.GetClosestMatchProperty(memberName, Serializer.PropertyCaseSensitivityHandling),
Property = contract.Properties.GetClosestMatchProperty(memberName, Serializer.PropertyCaseSensitivityHandling),
};
propertyValues.Add(creatorPropertyContext);

Expand Down Expand Up @@ -2367,7 +2367,7 @@ private object PopulateObject(object newObject, JsonReader reader, JsonObjectCon
{
// attempt exact case match first
// then try match ignoring case
JsonProperty? property = contract.Properties.GetClosestMatchProperty(propertyName);
JsonProperty? property = contract.Properties.GetClosestMatchProperty(propertyName, Serializer.PropertyCaseSensitivityHandling);

if (property == null)
{
Expand Down