From ae074a3469c031a344f21fb0864c38a6a92a6a3e Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 28 Oct 2016 13:25:52 +1300 Subject: [PATCH] -Fixed incorrectly parsing scan filter when name is quoted -Added support for multiple names in scan filter --- .../Linq/JsonPath/JPathExecuteTests.cs | 75 +++++++++++++++++++ Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs | 29 ++++--- .../Linq/JsonPath/ScanMultipleFilter.cs | 54 +++++++++++++ .../Newtonsoft.Json.Net20.csproj | 1 + .../Newtonsoft.Json.Net35.csproj | 1 + .../Newtonsoft.Json.Net40.csproj | 1 + .../Newtonsoft.Json.Portable.csproj | 1 + .../Newtonsoft.Json.Portable40.csproj | 1 + Src/Newtonsoft.Json/Newtonsoft.Json.csproj | 1 + 9 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 Src/Newtonsoft.Json/Linq/JsonPath/ScanMultipleFilter.cs diff --git a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs index 72dcc15d..2ca2a7e0 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs @@ -51,6 +51,81 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath [TestFixture] public class JPathExecuteTests : TestFixtureBase { + [Test] + public void ScanQuoted() + { + string json = @"{ + ""Node1"": { + ""Child1"": { + ""Name"": ""IsMe"", + ""TargetNode"": { + ""Prop1"": ""Val1"", + ""Prop2"": ""Val2"" + } + }, + ""My.Child.Node"": { + ""TargetNode"": { + ""Prop1"": ""Val1"", + ""Prop2"": ""Val2"" + } + } + }, + ""Node2"": { + ""TargetNode"": { + ""Prop1"": ""Val1"", + ""Prop2"": ""Val2"" + } + } +}"; + + JObject models = JObject.Parse(json); + + int result = models.SelectTokens("$..['My.Child.Node']").Count(); + Assert.AreEqual(1, result); + + result = models.SelectTokens("..['My.Child.Node']").Count(); + Assert.AreEqual(1, result); + } + + [Test] + public void ScanMultipleQuoted() + { + string json = @"{ + ""Node1"": { + ""Child1"": { + ""Name"": ""IsMe"", + ""TargetNode"": { + ""Prop1"": ""Val1"", + ""Prop2"": ""Val2"" + } + }, + ""My.Child.Node"": { + ""TargetNode"": { + ""Prop1"": ""Val3"", + ""Prop2"": ""Val4"" + } + } + }, + ""Node2"": { + ""TargetNode"": { + ""Prop1"": ""Val5"", + ""Prop2"": ""Val6"" + } + } +}"; + + JObject models = JObject.Parse(json); + + var results = models.SelectTokens("$..['My.Child.Node','Prop1','Prop2']").ToList(); + Assert.AreEqual("Val1", (string)results[0]); + Assert.AreEqual("Val2", (string)results[1]); + Assert.AreEqual(JTokenType.Object, results[2].Type); + Assert.AreEqual("Val3", (string)results[3]); + Assert.AreEqual("Val4", (string)results[4]); + Assert.AreEqual("Val5", (string)results[5]); + Assert.AreEqual("Val6", (string)results[6]); + } + [Test] public void ParseWithEmptyArrayContent() { diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs b/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs index 88d86912..abae9e8d 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs @@ -111,12 +111,11 @@ namespace Newtonsoft.Json.Linq.JsonPath member = null; } - PathFilter filter = (scan) ? (PathFilter)new ScanFilter { Name = member } : new FieldFilter { Name = member }; - filters.Add(filter); + filters.Add(CreatePathFilter(member, scan)); scan = false; } - filters.Add(ParseIndexer(currentChar)); + filters.Add(ParseIndexer(currentChar, scan)); _currentIndex++; currentPartStartIndex = _currentIndex; followingIndexer = true; @@ -141,8 +140,7 @@ namespace Newtonsoft.Json.Linq.JsonPath member = null; } - PathFilter filter = (scan) ? (PathFilter)new ScanFilter { Name = member } : new FieldFilter { Name = member }; - filters.Add(filter); + filters.Add(CreatePathFilter(member, scan)); scan = false; } if (_currentIndex + 1 < _expression.Length && _expression[_currentIndex + 1] == '.') @@ -182,8 +180,7 @@ namespace Newtonsoft.Json.Linq.JsonPath { member = null; } - PathFilter filter = (scan) ? (PathFilter)new ScanFilter { Name = member } : new FieldFilter { Name = member }; - filters.Add(filter); + filters.Add(CreatePathFilter(member, scan)); } else { @@ -197,7 +194,13 @@ namespace Newtonsoft.Json.Linq.JsonPath return atPathEnd; } - private PathFilter ParseIndexer(char indexerOpenChar) + private static PathFilter CreatePathFilter(string member, bool scan) + { + PathFilter filter = (scan) ? (PathFilter)new ScanFilter {Name = member} : new FieldFilter {Name = member}; + return filter; + } + + private PathFilter ParseIndexer(char indexerOpenChar, bool scan) { _currentIndex++; @@ -209,7 +212,7 @@ namespace Newtonsoft.Json.Linq.JsonPath if (_expression[_currentIndex] == '\'') { - return ParseQuotedField(indexerCloseChar); + return ParseQuotedField(indexerCloseChar, scan); } else if (_expression[_currentIndex] == '?') { @@ -710,7 +713,7 @@ namespace Newtonsoft.Json.Linq.JsonPath throw new JsonException("Could not read query operator."); } - private PathFilter ParseQuotedField(char indexerCloseChar) + private PathFilter ParseQuotedField(char indexerCloseChar, bool scan) { List fields = null; @@ -726,11 +729,13 @@ namespace Newtonsoft.Json.Linq.JsonPath if (fields != null) { fields.Add(field); - return new FieldMultipleFilter { Names = fields }; + return (scan) + ? (PathFilter)new ScanMultipleFilter { Names = fields } + : (PathFilter)new FieldMultipleFilter { Names = fields }; } else { - return new FieldFilter { Name = field }; + return CreatePathFilter(field, scan); } } else if (_expression[_currentIndex] == ',') diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/ScanMultipleFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/ScanMultipleFilter.cs new file mode 100644 index 00000000..c783927e --- /dev/null +++ b/Src/Newtonsoft.Json/Linq/JsonPath/ScanMultipleFilter.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; + +namespace Newtonsoft.Json.Linq.JsonPath +{ + internal class ScanMultipleFilter : PathFilter + { + public List Names { get; set; } + + public override IEnumerable ExecuteFilter(JToken root, IEnumerable current, bool errorWhenNoMatch) + { + foreach (JToken c in current) + { + JToken value = c; + JToken container = c; + + while (true) + { + if (container != null && container.HasValues) + { + value = container.First; + } + else + { + while (value != null && value != c && value == value.Parent.Last) + { + value = value.Parent; + } + + if (value == null || value == c) + { + break; + } + + value = value.Next; + } + + JProperty e = value as JProperty; + if (e != null) + { + foreach (string name in Names) + { + if (e.Name == name) + { + yield return e.Value; + } + } + } + + container = value as JContainer; + } + } + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj index b006ad57..5c49be45 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj @@ -125,6 +125,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj index d7e44d32..cec06ba6 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj @@ -139,6 +139,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj index ff8ae695..46474b2e 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj @@ -140,6 +140,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj index f6aaa861..6bd41106 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj @@ -130,6 +130,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj index 32e4a535..b48bfeab 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj @@ -135,6 +135,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index 44f7694b..e9e37c4e 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -131,6 +131,7 @@ +