From 9fe24483206c2ab962f661dbb1051a7601287e6d Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 20 Sep 2018 00:21:16 -0700 Subject: [PATCH] Add JObject.Property overload with a StringComparison and JsonMergeSettings.PropertyNameComparison (#1843) --- .../Linq/JObjectTests.cs | 23 ++++++++ Src/Newtonsoft.Json.Tests/Linq/MergeTests.cs | 35 ++++++++++++ Src/Newtonsoft.Json/Linq/JObject.cs | 56 ++++++++++++------- Src/Newtonsoft.Json/Linq/JsonMergeSettings.cs | 54 ++++++++++++++++++ 4 files changed, 147 insertions(+), 21 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Linq/JObjectTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JObjectTests.cs index cabc77f9..3e481989 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JObjectTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JObjectTests.cs @@ -2063,5 +2063,28 @@ Parameter name: arrayIndex"); Assert.AreEqual(1, (int)(JToken)value); } #endif + + [Test] + public void Property() + { + JObject a = new JObject(); + a["Name"] = "Name!"; + a["name"] = "name!"; + a["title"] = "Title!"; + + Assert.AreEqual(null, a.Property("NAME", StringComparison.Ordinal)); + Assert.AreEqual(null, a.Property("NAME")); + Assert.AreEqual(null, a.Property("TITLE")); + Assert.AreEqual(null, a.Property(null, StringComparison.Ordinal)); + Assert.AreEqual(null, a.Property(null, StringComparison.OrdinalIgnoreCase)); + Assert.AreEqual(null, a.Property(null)); + + // Return first match when ignoring case + Assert.AreEqual("Name", a.Property("NAME", StringComparison.OrdinalIgnoreCase).Name); + // Return exact match before ignoring case + Assert.AreEqual("name", a.Property("name", StringComparison.OrdinalIgnoreCase).Name); + // Return exact match without ignoring case + Assert.AreEqual("name", a.Property("name", StringComparison.Ordinal).Name); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Linq/MergeTests.cs b/Src/Newtonsoft.Json.Tests/Linq/MergeTests.cs index 59b8bd5c..6c86b8a3 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/MergeTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/MergeTests.cs @@ -666,5 +666,40 @@ namespace Newtonsoft.Json.Tests.Linq StringAssert.AreEqual(newJson, newFoo.ToString()); } + + [Test] + public void Merge_IgnorePropertyCase() + { + JObject o1 = JObject.Parse(@"{ + 'Id': '1', + 'Words': [ 'User' ] + }"); + JObject o2 = JObject.Parse(@"{ + 'Id': '1', + 'words': [ 'Name' ] + }"); + + o1.Merge(o2, new JsonMergeSettings + { + MergeArrayHandling = MergeArrayHandling.Concat, + MergeNullValueHandling = MergeNullValueHandling.Merge, + PropertyNameComparison = StringComparison.OrdinalIgnoreCase + }); + + Assert.IsNull(o1["words"]); + Assert.IsNotNull(o1["Words"]); + + JArray words = (JArray)o1["Words"]; + Assert.AreEqual("User", (string)words[0]); + Assert.AreEqual("Name", (string)words[1]); + } + + [Test] + public void MergeSettingsComparisonDefault() + { + JsonMergeSettings settings = new JsonMergeSettings(); + + Assert.AreEqual(StringComparison.Ordinal, settings.PropertyNameComparison); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JObject.cs b/Src/Newtonsoft.Json/Linq/JObject.cs index a5db689c..5bb9b4ab 100644 --- a/Src/Newtonsoft.Json/Linq/JObject.cs +++ b/Src/Newtonsoft.Json/Linq/JObject.cs @@ -175,7 +175,7 @@ namespace Newtonsoft.Json.Linq foreach (KeyValuePair contentItem in o) { - JProperty existingProperty = Property(contentItem.Key); + JProperty existingProperty = Property(contentItem.Key, settings?.PropertyNameComparison ?? StringComparison.Ordinal); if (existingProperty == null) { @@ -263,14 +263,44 @@ namespace Newtonsoft.Json.Linq /// The property name. /// A with the specified name or null. public JProperty Property(string name) + { + return Property(name, StringComparison.Ordinal); + } + + /// + /// Gets the with the specified name. + /// The exact name will be searched for first and if no matching property is found then + /// the will be used to match a property. + /// + /// The property name. + /// One of the enumeration values that specifies how the strings will be compared. + /// A matched with the specified name or null. + public JProperty Property(string name, StringComparison comparison) { if (name == null) { return null; } - _properties.TryGetValue(name, out JToken property); - return (JProperty)property; + if (_properties.TryGetValue(name, out JToken property)) + { + return (JProperty)property; + } + + // test above already uses this comparison so no need to repeat + if (comparison != StringComparison.Ordinal) + { + for (int i = 0; i < _properties.Count; i++) + { + JProperty p = (JProperty)_properties[i]; + if (string.Equals(p.Name, name, comparison)) + { + return p; + } + } + } + + return null; } /// @@ -509,25 +539,9 @@ namespace Newtonsoft.Json.Linq } // attempt to get value via dictionary first for performance - JProperty property = Property(propertyName); - if (property != null) - { - return property.Value; - } + var property = Property(propertyName, comparison); - // test above already uses this comparison so no need to repeat - if (comparison != StringComparison.Ordinal) - { - foreach (JProperty p in _properties) - { - if (string.Equals(p.Name, propertyName, comparison)) - { - return p.Value; - } - } - } - - return null; + return property?.Value; } /// diff --git a/Src/Newtonsoft.Json/Linq/JsonMergeSettings.cs b/Src/Newtonsoft.Json/Linq/JsonMergeSettings.cs index b3d94332..4de8fff6 100644 --- a/Src/Newtonsoft.Json/Linq/JsonMergeSettings.cs +++ b/Src/Newtonsoft.Json/Linq/JsonMergeSettings.cs @@ -1,3 +1,28 @@ +#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; namespace Newtonsoft.Json.Linq @@ -9,6 +34,15 @@ namespace Newtonsoft.Json.Linq { private MergeArrayHandling _mergeArrayHandling; private MergeNullValueHandling _mergeNullValueHandling; + private StringComparison _propertyNameComparison; + + /// + /// Initializes a new instance of the class. + /// + public JsonMergeSettings() + { + _propertyNameComparison = StringComparison.Ordinal; + } /// /// Gets or sets the method used when merging JSON arrays. @@ -45,5 +79,25 @@ namespace Newtonsoft.Json.Linq _mergeNullValueHandling = value; } } + + /// + /// Gets or sets the comparison used to match property names while merging. + /// The exact property name will be searched for first and if no matching property is found then + /// the will be used to match a property. + /// + /// The comparison used to match property names while merging. + public StringComparison PropertyNameComparison + { + get => _propertyNameComparison; + set + { + if (value < StringComparison.CurrentCulture || value > StringComparison.OrdinalIgnoreCase) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _propertyNameComparison = value; + } + } } } \ No newline at end of file