diff --git a/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs index feff2573..2ca5aaab 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs @@ -433,7 +433,7 @@ namespace Newtonsoft.Json.Tests.Converters [EnumMember(Value = "baz")] SerializeAsBaz = 0x4, - }; + } [JsonConverter(typeof(StringEnumConverter))] private enum Bar @@ -444,7 +444,7 @@ namespace Newtonsoft.Json.Tests.Converters [EnumMember(Value = "baz")] SerializeAsBaz - }; + } #endif } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Linq/JPathTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JPathTests.cs index e719ed74..7247081b 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JPathTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JPathTests.cs @@ -61,6 +61,47 @@ namespace Newtonsoft.Json.Tests.Linq Assert.AreEqual("Blah", ((FieldFilter)path.Parts[0]).Name); } + [Test] + public void WildcardPropertyWithRoot() + { + JPath path = new JPath("$.*"); + Assert.AreEqual(1, path.Parts.Count); + Assert.AreEqual(null, ((FieldFilter)path.Parts[0]).Name); + } + + [Test] + public void WildcardArrayWithRoot() + { + JPath path = new JPath("$.[*]"); + Assert.AreEqual(1, path.Parts.Count); + Assert.AreEqual(null, ((ArrayIndexFilter)path.Parts[0]).Index); + } + + [Test] + public void WildcardArray() + { + JPath path = new JPath("[*]"); + Assert.AreEqual(1, path.Parts.Count); + Assert.AreEqual(null, ((ArrayIndexFilter)path.Parts[0]).Index); + } + + [Test] + public void WildcardArrayWithProperty() + { + JPath path = new JPath("[*].derp"); + Assert.AreEqual(2, path.Parts.Count); + Assert.AreEqual(null, ((ArrayIndexFilter)path.Parts[0]).Index); + Assert.AreEqual("derp", ((FieldFilter)path.Parts[1]).Name); + } + + [Test] + public void QuotedWildcardPropertyWithRoot() + { + JPath path = new JPath("$.['*']"); + Assert.AreEqual(1, path.Parts.Count); + Assert.AreEqual("*", ((FieldFilter)path.Parts[0]).Name); + } + [Test] public void SingleScanWithRoot() { @@ -69,6 +110,14 @@ namespace Newtonsoft.Json.Tests.Linq Assert.AreEqual("Blah", ((ScanFilter)path.Parts[0]).Name); } + [Test] + public void WildcardScanWithRoot() + { + JPath path = new JPath("$..*"); + Assert.AreEqual(1, path.Parts.Count); + Assert.AreEqual(null, ((ScanFilter)path.Parts[0]).Name); + } + [Test] public void TwoProperties() { @@ -216,6 +265,20 @@ namespace Newtonsoft.Json.Tests.Linq Assert.AreEqual(1, (int)t); } + [Test] + public void EvaluateWildcardProperty() + { + JObject o = new JObject( + new JProperty("Blah", 1), + new JProperty("Blah2", 2)); + + IList t = o.SelectTokens("$.*").ToList(); + Assert.IsNotNull(t); + Assert.AreEqual(2, t.Count); + Assert.AreEqual(1, (int)t[0]); + Assert.AreEqual(2, (int)t[1]); + } + [Test] public void QuoteName() { @@ -329,6 +392,20 @@ namespace Newtonsoft.Json.Tests.Linq Assert.AreEqual(2, (int)t); } + [Test] + public void EvaluateWildcardArray() + { + JArray a = new JArray(1, 2, 3, 4); + + List t = a.SelectTokens("[*]").ToList(); + Assert.IsNotNull(t); + Assert.AreEqual(4, t.Count); + Assert.AreEqual(1, (int)t[0]); + Assert.AreEqual(2, (int)t[1]); + Assert.AreEqual(3, (int)t[2]); + Assert.AreEqual(4, (int)t[3]); + } + [Test] public void EvaluateArrayMultipleIndexes() { @@ -342,6 +419,77 @@ namespace Newtonsoft.Json.Tests.Linq Assert.AreEqual(1, (int)t.ElementAt(2)); } + [Test] + public void EvaluateScan() + { + JObject o1 = new JObject{ {"Name", 1} }; + JObject o2 = new JObject{ {"Name", 2} }; + JArray a = new JArray(o1, o2); + + IList t = a.SelectTokens("$..Name").ToList(); + Assert.IsNotNull(t); + Assert.AreEqual(2, t.Count); + Assert.AreEqual(1, (int)t[0]); + Assert.AreEqual(2, (int)t[1]); + } + + [Test] + public void EvaluateWildcardScan() + { + JObject o1 = new JObject { { "Name", 1 } }; + JObject o2 = new JObject { { "Name", 2 } }; + JArray a = new JArray(o1, o2); + + IList t = a.SelectTokens("$..*").ToList(); + Assert.IsNotNull(t); + Assert.AreEqual(5, t.Count); + Assert.IsTrue(JToken.DeepEquals(a, t[0])); + Assert.IsTrue(JToken.DeepEquals(o1, t[1])); + Assert.AreEqual(1, (int)t[2]); + Assert.IsTrue(JToken.DeepEquals(o2, t[3])); + Assert.AreEqual(2, (int)t[4]); + } + + [Test] + public void EvaluateScanNestResults() + { + JObject o1 = new JObject { { "Name", 1 } }; + JObject o2 = new JObject { { "Name", 2 } }; + JObject o3 = new JObject { { "Name", new JObject { { "Name", new JArray(3) } } } }; + JArray a = new JArray(o1, o2, o3); + + IList t = a.SelectTokens("$..Name").ToList(); + Assert.IsNotNull(t); + Assert.AreEqual(4, t.Count); + Assert.AreEqual(1, (int)t[0]); + Assert.AreEqual(2, (int)t[1]); + Assert.IsTrue(JToken.DeepEquals(new JObject { { "Name", new JArray(3) } }, t[2])); + Assert.IsTrue(JToken.DeepEquals(new JArray(3), t[3])); + } + + [Test] + public void EvaluateWildcardScanNestResults() + { + JObject o1 = new JObject { { "Name", 1 } }; + JObject o2 = new JObject { { "Name", 2 } }; + JObject o3 = new JObject { { "Name", new JObject { { "Name", new JArray(3) } } } }; + JArray a = new JArray(o1, o2, o3); + + IList t = a.SelectTokens("$..*").ToList(); + Assert.IsNotNull(t); + Assert.AreEqual(9, t.Count); + + Assert.IsTrue(JToken.DeepEquals(a, t[0])); + Assert.IsTrue(JToken.DeepEquals(o1, t[1])); + Assert.AreEqual(1, (int)t[2]); + Assert.IsTrue(JToken.DeepEquals(o2, t[3])); + Assert.AreEqual(2, (int)t[4]); + Assert.IsTrue(JToken.DeepEquals(o3, t[5])); + Assert.IsTrue(JToken.DeepEquals(new JObject { { "Name", new JArray(3) } }, t[6])); + Assert.IsTrue(JToken.DeepEquals(new JArray(3), t[7])); + Assert.AreEqual(3, (int)t[8]); + } + [Test] public void EvaluateSinglePropertyReturningArray() { diff --git a/Src/Newtonsoft.Json/Linq/JPath.cs b/Src/Newtonsoft.Json/Linq/JPath.cs index c2f6886b..16811302 100644 --- a/Src/Newtonsoft.Json/Linq/JPath.cs +++ b/Src/Newtonsoft.Json/Linq/JPath.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Xml.Linq; using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json.Linq @@ -48,7 +49,7 @@ namespace Newtonsoft.Json.Linq internal class ArrayIndexFilter : PathFilter { - public int Index { get; set; } + public int? Index { get; set; } public override FilterType Type { @@ -184,6 +185,8 @@ namespace Newtonsoft.Json.Linq if (_currentIndex > currentPartStartIndex) { string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex); + if (member == "*") + member = null; PathFilter filter = (scan) ? (PathFilter)new ScanFilter() { Name = member } : new FieldFilter() { Name = member }; Parts.Add(filter); scan = false; @@ -208,6 +211,8 @@ namespace Newtonsoft.Json.Linq if (_currentIndex > currentPartStartIndex) { string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex); + if (member == "*") + member = null; PathFilter filter = (scan) ? (PathFilter)new ScanFilter() { Name = member } : new FieldFilter() { Name = member }; Parts.Add(filter); } @@ -285,6 +290,17 @@ namespace Newtonsoft.Json.Linq start = _currentIndex + 1; } + else if (currentCharacter == '*') + { + _currentIndex++; + EnsureLength("Path ended with open indexer."); + + if (_expression[_currentIndex] != indexerCloseChar) + throw new JsonException("Unexpected character while parsing path indexer: " + currentCharacter); + + Parts.Add(new ArrayIndexFilter()); + return; + } else if (!char.IsDigit(currentCharacter)) { throw new JsonException("Unexpected character while parsing path indexer: " + currentCharacter); @@ -397,13 +413,16 @@ namespace Newtonsoft.Json.Linq switch (filter.Type) { case FilterType.ArrayIndex: - current = ExecuteArrayIndexFilter(current, errorWhenNoMatch, (ArrayIndexFilter)filter); + current = ExecuteArrayIndexFilter(current, errorWhenNoMatch, (ArrayIndexFilter) filter); break; case FilterType.ArrayMultipleIndex: - current = ExecuteArrayMultipleIndexFilter(current, errorWhenNoMatch, (ArrayMultipleIndexFilter)filter); + current = ExecuteArrayMultipleIndexFilter(current, errorWhenNoMatch, (ArrayMultipleIndexFilter) filter); break; case FilterType.Field: - current = ExecuteFieldFilter(current, errorWhenNoMatch, (FieldFilter)filter); + current = ExecuteFieldFilter(current, errorWhenNoMatch, (FieldFilter) filter); + break; + case FilterType.Scan: + current = ExecuteScanFilter(current, errorWhenNoMatch, (ScanFilter) filter); break; default: throw new ArgumentOutOfRangeException(); @@ -413,6 +432,56 @@ namespace Newtonsoft.Json.Linq return current; } + private static IEnumerable GetDescendants(IEnumerable source, string name) + { + foreach (JToken root in source) + { + if (name == null) + yield return root; + + JToken current = root; + JToken container = root; + + while (true) + { + if (container != null) + { + current = container.First; + } + else + { + while (current != null && current != root && current == current.Parent.Last) + { + current = current.Parent; + } + + if (current == null || current == root) + break; + current = current.Next; + } + + JProperty e = current as JProperty; + if (e != null) + { + if (e.Name == name) + yield return e.Value; + } + else + { + if (name == null) + yield return current; + } + + container = current as JContainer; + } + } + } + + private IEnumerable ExecuteScanFilter(IEnumerable current, bool errorWhenNoMatch, ScanFilter scanFilter) + { + return GetDescendants(current, scanFilter.Name); + } + private IEnumerable ExecuteArrayMultipleIndexFilter(IEnumerable current, bool errorWhenNoMatch, ArrayMultipleIndexFilter arrayMultipleIndexFilter) { foreach (JToken t in current) @@ -434,17 +503,29 @@ namespace Newtonsoft.Json.Linq JObject o = t as JObject; if (o != null) { - JToken v = o[fieldFilter.Name]; + if (fieldFilter.Name != null) + { + JToken v = o[fieldFilter.Name]; - if (v == null && errorWhenNoMatch) - throw new JsonException("Property '{0}' does not exist on JObject.".FormatWith(CultureInfo.InvariantCulture, fieldFilter.Name)); + if (v == null && errorWhenNoMatch) + throw new JsonException("Property '{0}' does not exist on JObject.".FormatWith(CultureInfo.InvariantCulture, fieldFilter.Name)); - yield return v; + yield return v; + } + else + { + // todo - error when no results + + foreach (KeyValuePair p in o) + { + yield return p.Value; + } + } } else { if (errorWhenNoMatch) - throw new JsonException("Property '{0}' not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, fieldFilter.Name, t.GetType().Name)); + throw new JsonException("Property '{0}' not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, fieldFilter.Name ?? "*", t.GetType().Name)); } } } @@ -453,10 +534,28 @@ namespace Newtonsoft.Json.Linq { foreach (JToken t in current) { - JToken v = GetTokenIndex(t, errorWhenNoMatch, arrayIndexFilter.Index); + if (arrayIndexFilter.Index != null) + { + JToken v = GetTokenIndex(t, errorWhenNoMatch, arrayIndexFilter.Index.Value); - if (v != null) - yield return v; + if (v != null) + yield return v; + } + else + { + if (t is JArray || t is JConstructor) + { + foreach (JToken v in t) + { + yield return v; + } + } + else + { + if (errorWhenNoMatch) + throw new JsonException("Index * not valid on {0}.".FormatWith(CultureInfo.InvariantCulture, t.GetType().Name)); + } + } } }