-JsonPath++

This commit is contained in:
James Newton-King
2013-11-16 12:30:47 +13:00
parent 513ced87ca
commit 5ce7d8d3dc
13 changed files with 847 additions and 509 deletions
@@ -25,6 +25,7 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq.JsonPath;
#if !NETFX_CORE
using NUnit.Framework;
#else
@@ -40,219 +41,11 @@ using System.Linq;
#endif
namespace Newtonsoft.Json.Tests.Linq
namespace Newtonsoft.Json.Tests.Linq.JsonPath
{
[TestFixture]
public class JPathTests : TestFixtureBase
public class JPathExecuteTests : TestFixtureBase
{
[Test]
public void SingleProperty()
{
JPath path = new JPath("Blah");
Assert.AreEqual(1, path.Parts.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Parts[0]).Name);
}
[Test]
public void SinglePropertyWithRoot()
{
JPath path = new JPath("$.Blah");
Assert.AreEqual(1, path.Parts.Count);
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()
{
JPath path = new JPath("$..Blah");
Assert.AreEqual(1, path.Parts.Count);
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()
{
JPath path = new JPath("Blah.Two");
Assert.AreEqual(2, path.Parts.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Parts[0]).Name);
Assert.AreEqual("Two", ((FieldFilter)path.Parts[1]).Name);
}
[Test]
public void OnePropertyOneScan()
{
JPath path = new JPath("Blah..Two");
Assert.AreEqual(2, path.Parts.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Parts[0]).Name);
Assert.AreEqual("Two", ((ScanFilter)path.Parts[1]).Name);
}
[Test]
public void SinglePropertyAndIndexer()
{
JPath path = new JPath("Blah[0]");
Assert.AreEqual(2, path.Parts.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Parts[0]).Name);
Assert.AreEqual(0, ((ArrayIndexFilter)path.Parts[1]).Index);
}
[Test]
public void SinglePropertyAndFilter()
{
JPath path = new JPath("Blah[?(@.name=='hi')]");
Assert.AreEqual(2, path.Parts.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Parts[0]).Name);
List<object> expressions = ((QueryFilter)path.Parts[1]).Expression;
Assert.AreEqual(1, expressions.Count);
}
[Test]
public void MultiplePropertiesAndIndexers()
{
JPath path = new JPath("Blah[0]..Two.Three[1].Four");
Assert.AreEqual(6, path.Parts.Count);
Assert.AreEqual(FilterType.Field, path.Parts[0].Type);
Assert.AreEqual("Blah", ((FieldFilter) path.Parts[0]).Name);
Assert.AreEqual(FilterType.ArrayIndex, path.Parts[1].Type);
Assert.AreEqual(0, ((ArrayIndexFilter) path.Parts[1]).Index);
Assert.AreEqual(FilterType.Scan, path.Parts[2].Type);
Assert.AreEqual("Two", ((ScanFilter)path.Parts[2]).Name);
Assert.AreEqual("Three", ((FieldFilter)path.Parts[3]).Name);
Assert.AreEqual(1, ((ArrayIndexFilter)path.Parts[4]).Index);
Assert.AreEqual("Four", ((FieldFilter)path.Parts[5]).Name);
}
[Test]
public void BadCharactersInIndexer()
{
ExceptionAssert.Throws<JsonException>(
@"Unexpected character while parsing path indexer: [",
() => { new JPath("Blah[[0]].Two.Three[1].Four"); });
}
[Test]
public void UnclosedIndexer()
{
ExceptionAssert.Throws<JsonException>(
@"Path ended with open indexer.",
() => { new JPath("Blah[0"); });
}
//[Test]
//public void AdditionalDots()
//{
// JPath path = new JPath(".Blah..[0]..Two.Three....[1].Four.");
// Assert.AreEqual(6, path.Parts.Count);
// Assert.AreEqual("Blah", path.Parts[0]);
// Assert.AreEqual(0, path.Parts[1]);
// Assert.AreEqual("Two", path.Parts[2]);
// Assert.AreEqual("Three", path.Parts[3]);
// Assert.AreEqual(1, path.Parts[4]);
// Assert.AreEqual("Four", path.Parts[5]);
//}
[Test]
public void IndexerOnly()
{
JPath path = new JPath("[111119990]");
Assert.AreEqual(1, path.Parts.Count);
Assert.AreEqual(111119990, ((ArrayIndexFilter)path.Parts[0]).Index);
}
[Test]
public void MultipleIndexes()
{
JPath path = new JPath("[111119990,3]");
Assert.AreEqual(1, path.Parts.Count);
Assert.AreEqual(2, ((ArrayMultipleIndexFilter)path.Parts[0]).Indexes.Count);
Assert.AreEqual(111119990, ((ArrayMultipleIndexFilter)path.Parts[0]).Indexes[0]);
Assert.AreEqual(3, ((ArrayMultipleIndexFilter)path.Parts[0]).Indexes[1]);
}
[Test]
public void EmptyIndexer()
{
ExceptionAssert.Throws<JsonException>(
"Array index expected.",
() => { new JPath("[]"); });
}
[Test]
public void IndexerCloseInProperty()
{
ExceptionAssert.Throws<JsonException>(
"Unexpected character while parsing path: ]",
() => { new JPath("]"); });
}
[Test]
public void AdjacentIndexers()
{
JPath path = new JPath("[1][0][0][" + int.MaxValue + "]");
Assert.AreEqual(4, path.Parts.Count);
Assert.AreEqual(1, ((ArrayIndexFilter)path.Parts[0]).Index);
Assert.AreEqual(0, ((ArrayIndexFilter)path.Parts[1]).Index);
Assert.AreEqual(0, ((ArrayIndexFilter)path.Parts[2]).Index);
Assert.AreEqual(int.MaxValue, ((ArrayIndexFilter)path.Parts[3]).Index);
}
[Test]
public void MissingDotAfterIndexer()
{
ExceptionAssert.Throws<JsonException>(
"Unexpected character following indexer: B",
() => { new JPath("[1]Blah"); });
}
[Test]
public void EvaluateSingleProperty()
{
@@ -392,6 +185,54 @@ namespace Newtonsoft.Json.Tests.Linq
Assert.AreEqual(2, (int)t);
}
[Test]
public void EvaluateArraySlice()
{
JArray a = new JArray(1, 2, 3, 4, 5, 6, 7, 8, 9);
IList<JToken> t = null;
t = a.SelectTokens("[-3:]").ToList();
Assert.AreEqual(3, t.Count);
Assert.AreEqual(7, (int)t[0]);
Assert.AreEqual(8, (int)t[1]);
Assert.AreEqual(9, (int)t[2]);
t = a.SelectTokens("[-1:-2:-1]").ToList();
Assert.AreEqual(1, t.Count);
Assert.AreEqual(9, (int)t[0]);
t = a.SelectTokens("[-2:-1]").ToList();
Assert.AreEqual(1, t.Count);
Assert.AreEqual(8, (int)t[0]);
t = a.SelectTokens("[1:1]").ToList();
Assert.AreEqual(0, t.Count);
t = a.SelectTokens("[1:2]").ToList();
Assert.AreEqual(1, t.Count);
Assert.AreEqual(2, (int)t[0]);
t = a.SelectTokens("[::-1]").ToList();
Assert.AreEqual(9, t.Count);
Assert.AreEqual(9, (int)t[0]);
Assert.AreEqual(8, (int)t[1]);
Assert.AreEqual(7, (int)t[2]);
Assert.AreEqual(6, (int)t[3]);
Assert.AreEqual(5, (int)t[4]);
Assert.AreEqual(4, (int)t[5]);
Assert.AreEqual(3, (int)t[6]);
Assert.AreEqual(2, (int)t[7]);
Assert.AreEqual(1, (int)t[8]);
t = a.SelectTokens("[::-2]").ToList();
Assert.AreEqual(5, t.Count);
Assert.AreEqual(9, (int)t[0]);
Assert.AreEqual(7, (int)t[1]);
Assert.AreEqual(5, (int)t[2]);
Assert.AreEqual(3, (int)t[3]);
Assert.AreEqual(1, (int)t[4]);
}
[Test]
public void EvaluateWildcardArray()
{
@@ -0,0 +1,383 @@
#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 Newtonsoft.Json.Linq.JsonPath;
#if !NETFX_CORE
using NUnit.Framework;
#else
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute;
using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute;
#endif
using Newtonsoft.Json.Linq;
#if NET20
using Newtonsoft.Json.Utilities.LinqBridge;
#else
using System.Linq;
#endif
namespace Newtonsoft.Json.Tests.Linq.JsonPath
{
[TestFixture]
public class JPathParseTests : TestFixtureBase
{
[Test]
public void SingleProperty()
{
JPath path = new JPath("Blah");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
}
[Test]
public void SingleQuotedProperty()
{
JPath path = new JPath("['Blah']");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
}
[Test]
public void SingleQuotedPropertyWithWhitespace()
{
JPath path = new JPath("[ 'Blah' ]");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
}
[Test]
public void SingleQuotedPropertyWithDots()
{
JPath path = new JPath("['Blah.Ha']");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual("Blah.Ha", ((FieldFilter)path.Filters[0]).Name);
}
[Test]
public void SingleQuotedPropertyWithBrackets()
{
JPath path = new JPath("['[*]']");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual("[*]", ((FieldFilter)path.Filters[0]).Name);
}
[Test]
public void SinglePropertyWithRoot()
{
JPath path = new JPath("$.Blah");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
}
[Test]
public void SinglePropertyWithRootWithStartAndEndWhitespace()
{
JPath path = new JPath(" $.Blah ");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
}
[Test]
public void RootWithBadWhitespace()
{
ExceptionAssert.Throws<JsonException>(
@"Unexpected character while parsing path: ",
() => { new JPath("$ .Blah"); });
}
[Test]
public void RootWithBadWhitespace2()
{
ExceptionAssert.Throws<JsonException>(
@"Unexpected character while parsing path: ",
() => { new JPath("$. Blah"); });
}
[Test]
public void WildcardPropertyWithRoot()
{
JPath path = new JPath("$.*");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual(null, ((FieldFilter)path.Filters[0]).Name);
}
[Test]
public void WildcardArrayWithRoot()
{
JPath path = new JPath("$.[*]");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual(null, ((ArrayIndexFilter)path.Filters[0]).Index);
}
[Test]
public void WildcardArray()
{
JPath path = new JPath("[*]");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual(null, ((ArrayIndexFilter)path.Filters[0]).Index);
}
[Test]
public void WildcardArrayWithProperty()
{
JPath path = new JPath("[ * ].derp");
Assert.AreEqual(2, path.Filters.Count);
Assert.AreEqual(null, ((ArrayIndexFilter)path.Filters[0]).Index);
Assert.AreEqual("derp", ((FieldFilter)path.Filters[1]).Name);
}
[Test]
public void QuotedWildcardPropertyWithRoot()
{
JPath path = new JPath("$.['*']");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual("*", ((FieldFilter)path.Filters[0]).Name);
}
[Test]
public void SingleScanWithRoot()
{
JPath path = new JPath("$..Blah");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual("Blah", ((ScanFilter)path.Filters[0]).Name);
}
[Test]
public void WildcardScanWithRoot()
{
JPath path = new JPath("$..*");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual(null, ((ScanFilter)path.Filters[0]).Name);
}
[Test]
public void WildcardScanWithRootWithWhitespace()
{
JPath path = new JPath("$..* ");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual(null, ((ScanFilter)path.Filters[0]).Name);
}
[Test]
public void TwoProperties()
{
JPath path = new JPath("Blah.Two");
Assert.AreEqual(2, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
Assert.AreEqual("Two", ((FieldFilter)path.Filters[1]).Name);
}
[Test]
public void OnePropertyOneScan()
{
JPath path = new JPath("Blah..Two");
Assert.AreEqual(2, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
Assert.AreEqual("Two", ((ScanFilter)path.Filters[1]).Name);
}
[Test]
public void SinglePropertyAndIndexer()
{
JPath path = new JPath("Blah[0]");
Assert.AreEqual(2, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
Assert.AreEqual(0, ((ArrayIndexFilter)path.Filters[1]).Index);
}
[Test]
public void SinglePropertyAndFilter()
{
JPath path = new JPath("Blah[?(@.name=='hi')]");
Assert.AreEqual(2, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
List<object> expressions = ((QueryFilter)path.Filters[1]).Expression;
Assert.AreEqual(1, expressions.Count);
}
[Test]
public void MultiplePropertiesAndIndexers()
{
JPath path = new JPath("Blah[0]..Two.Three[1].Four");
Assert.AreEqual(6, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter) path.Filters[0]).Name);
Assert.AreEqual(0, ((ArrayIndexFilter) path.Filters[1]).Index);
Assert.AreEqual("Two", ((ScanFilter)path.Filters[2]).Name);
Assert.AreEqual("Three", ((FieldFilter)path.Filters[3]).Name);
Assert.AreEqual(1, ((ArrayIndexFilter)path.Filters[4]).Index);
Assert.AreEqual("Four", ((FieldFilter)path.Filters[5]).Name);
}
[Test]
public void BadCharactersInIndexer()
{
ExceptionAssert.Throws<JsonException>(
@"Unexpected character while parsing path indexer: [",
() => { new JPath("Blah[[0]].Two.Three[1].Four"); });
}
[Test]
public void UnclosedIndexer()
{
ExceptionAssert.Throws<JsonException>(
@"Path ended with open indexer.",
() => { new JPath("Blah[0"); });
}
[Test]
public void IndexerOnly()
{
JPath path = new JPath("[111119990]");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual(111119990, ((ArrayIndexFilter)path.Filters[0]).Index);
}
[Test]
public void IndexerOnlyWithWhitespace()
{
JPath path = new JPath("[ 10 ]");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual(10, ((ArrayIndexFilter)path.Filters[0]).Index);
}
[Test]
public void MultipleIndexes()
{
JPath path = new JPath("[111119990,3]");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual(2, ((ArrayMultipleIndexFilter)path.Filters[0]).Indexes.Count);
Assert.AreEqual(111119990, ((ArrayMultipleIndexFilter)path.Filters[0]).Indexes[0]);
Assert.AreEqual(3, ((ArrayMultipleIndexFilter)path.Filters[0]).Indexes[1]);
}
[Test]
public void MultipleIndexesWithWhitespace()
{
JPath path = new JPath("[ 111119990 , 3 ]");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual(2, ((ArrayMultipleIndexFilter)path.Filters[0]).Indexes.Count);
Assert.AreEqual(111119990, ((ArrayMultipleIndexFilter)path.Filters[0]).Indexes[0]);
Assert.AreEqual(3, ((ArrayMultipleIndexFilter)path.Filters[0]).Indexes[1]);
}
[Test]
public void SlicingIndexAll()
{
JPath path = new JPath("[111119990:3:2]");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual(111119990, ((ArraySliceFilter)path.Filters[0]).Start);
Assert.AreEqual(3, ((ArraySliceFilter)path.Filters[0]).End);
Assert.AreEqual(2, ((ArraySliceFilter)path.Filters[0]).Step);
}
[Test]
public void SlicingIndex()
{
JPath path = new JPath("[111119990:3]");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual(111119990, ((ArraySliceFilter)path.Filters[0]).Start);
Assert.AreEqual(3, ((ArraySliceFilter)path.Filters[0]).End);
Assert.AreEqual(null, ((ArraySliceFilter)path.Filters[0]).Step);
}
[Test]
public void SlicingIndexNegative()
{
JPath path = new JPath("[-111119990:-3:-2]");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual(-111119990, ((ArraySliceFilter)path.Filters[0]).Start);
Assert.AreEqual(-3, ((ArraySliceFilter)path.Filters[0]).End);
Assert.AreEqual(-2, ((ArraySliceFilter)path.Filters[0]).Step);
}
[Test]
public void SlicingIndexEmptyStop()
{
JPath path = new JPath("[ -3 : ]");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual(-3, ((ArraySliceFilter)path.Filters[0]).Start);
Assert.AreEqual(null, ((ArraySliceFilter)path.Filters[0]).End);
Assert.AreEqual(null, ((ArraySliceFilter)path.Filters[0]).Step);
}
[Test]
public void SlicingIndexEmptyStart()
{
JPath path = new JPath("[ : 1 : ]");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual(null, ((ArraySliceFilter)path.Filters[0]).Start);
Assert.AreEqual(1, ((ArraySliceFilter)path.Filters[0]).End);
Assert.AreEqual(null, ((ArraySliceFilter)path.Filters[0]).Step);
}
[Test]
public void SlicingIndexWhitespace()
{
JPath path = new JPath("[ -111119990 : -3 : -2 ]");
Assert.AreEqual(1, path.Filters.Count);
Assert.AreEqual(-111119990, ((ArraySliceFilter)path.Filters[0]).Start);
Assert.AreEqual(-3, ((ArraySliceFilter)path.Filters[0]).End);
Assert.AreEqual(-2, ((ArraySliceFilter)path.Filters[0]).Step);
}
[Test]
public void EmptyIndexer()
{
ExceptionAssert.Throws<JsonException>(
"Array index expected.",
() => { new JPath("[]"); });
}
[Test]
public void IndexerCloseInProperty()
{
ExceptionAssert.Throws<JsonException>(
"Unexpected character while parsing path: ]",
() => { new JPath("]"); });
}
[Test]
public void AdjacentIndexers()
{
JPath path = new JPath("[1][0][0][" + int.MaxValue + "]");
Assert.AreEqual(4, path.Filters.Count);
Assert.AreEqual(1, ((ArrayIndexFilter)path.Filters[0]).Index);
Assert.AreEqual(0, ((ArrayIndexFilter)path.Filters[1]).Index);
Assert.AreEqual(0, ((ArrayIndexFilter)path.Filters[2]).Index);
Assert.AreEqual(int.MaxValue, ((ArrayIndexFilter)path.Filters[3]).Index);
}
[Test]
public void MissingDotAfterIndexer()
{
ExceptionAssert.Throws<JsonException>(
"Unexpected character following indexer: B",
() => { new JPath("[1]Blah"); });
}
}
}
@@ -207,7 +207,8 @@
<Compile Include="Linq\JArrayTests.cs" />
<Compile Include="Linq\JConstructorTests.cs" />
<Compile Include="Linq\JObjectTests.cs" />
<Compile Include="Linq\JPathTests.cs" />
<Compile Include="Linq\JsonPath\JPathExecuteTests.cs" />
<Compile Include="Linq\JsonPath\JPathParseTests.cs" />
<Compile Include="Linq\JPropertyTests.cs" />
<Compile Include="Linq\JRawTests.cs" />
<Compile Include="Linq\JTokenEqualityComparerTests.cs" />
+1
View File
@@ -25,6 +25,7 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq.JsonPath;
#if !(NET35 || NET20 || PORTABLE40)
using System.Dynamic;
using System.Linq.Expressions;
@@ -0,0 +1,40 @@
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json.Utilities;
namespace Newtonsoft.Json.Linq.JsonPath
{
internal class ArrayIndexFilter : PathFilter
{
public int? Index { get; set; }
public override IEnumerable<JToken> ExecuteFilter(IEnumerable<JToken> current, bool errorWhenNoMatch)
{
foreach (JToken t in current)
{
if (Index != null)
{
JToken v = GetTokenIndex(t, errorWhenNoMatch, Index.Value);
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));
}
}
}
}
}
}
@@ -0,0 +1,23 @@
using System.Collections.Generic;
namespace Newtonsoft.Json.Linq.JsonPath
{
internal class ArrayMultipleIndexFilter : PathFilter
{
public List<int> Indexes { get; set; }
public override IEnumerable<JToken> ExecuteFilter(IEnumerable<JToken> current, bool errorWhenNoMatch)
{
foreach (JToken t in current)
{
foreach (int i in Indexes)
{
JToken v = GetTokenIndex(t, errorWhenNoMatch, i);
if (v != null)
yield return v;
}
}
}
}
}
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
namespace Newtonsoft.Json.Linq.JsonPath
{
internal class ArraySliceFilter : PathFilter
{
public int? Start { get; set; }
public int? End { get; set; }
public int? Step { get; set; }
public override IEnumerable<JToken> ExecuteFilter(IEnumerable<JToken> current, bool errorWhenNoMatch)
{
if (Step == 0)
throw new JsonException("Step cannot be zero.");
foreach (JToken t in current)
{
IList<JToken> sourceCollection = t as IList<JToken> ?? new List<JToken>(t);
// nothing to slice
if (sourceCollection.Count == 0)
yield break;
// set defaults for null arguments
int stepCount = Step ?? 1;
int startIndex = Start ?? ((stepCount > 0) ? 0 : sourceCollection.Count - 1);
int stopIndex = End ?? ((stepCount > 0) ? sourceCollection.Count : -1);
// start from the end of the list if start is negitive
if (Start < 0) startIndex = sourceCollection.Count + startIndex;
// end from the start of the list if stop is negitive
if (End < 0) stopIndex = sourceCollection.Count + stopIndex;
// ensure indexes keep within collection bounds
startIndex = Math.Max(startIndex, (stepCount > 0) ? 0 : int.MinValue);
startIndex = Math.Min(startIndex, (stepCount > 0) ? sourceCollection.Count : sourceCollection.Count - 1);
stopIndex = Math.Max(stopIndex, -1);
stopIndex = Math.Min(stopIndex, sourceCollection.Count);
for (int i = startIndex; (stepCount > 0) ? i < stopIndex : i > stopIndex; i += stepCount)
{
yield return sourceCollection[i];
}
}
}
}
}
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json.Utilities;
namespace Newtonsoft.Json.Linq.JsonPath
{
internal class FieldFilter : PathFilter
{
public string Name { get; set; }
public override IEnumerable<JToken> ExecuteFilter(IEnumerable<JToken> current, bool errorWhenNoMatch)
{
foreach (JToken t in current)
{
JObject o = t as JObject;
if (o != null)
{
if (Name != null)
{
JToken v = o[Name];
if (v == null && errorWhenNoMatch)
throw new JsonException("Property '{0}' does not exist on JObject.".FormatWith(CultureInfo.InvariantCulture, Name));
yield return v;
}
else
{
// todo - error when no results
foreach (KeyValuePair<string, JToken> p in o)
{
yield return p.Value;
}
}
}
else
{
if (errorWhenNoMatch)
throw new JsonException("Property '{0}' not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, Name ?? "*", t.GetType().Name));
}
}
}
}
}
@@ -30,89 +30,12 @@ using System.Linq;
using System.Xml.Linq;
using Newtonsoft.Json.Utilities;
namespace Newtonsoft.Json.Linq
namespace Newtonsoft.Json.Linq.JsonPath
{
internal enum FilterType
{
ArrayIndex,
ArrayMultipleIndex,
ArraySlice,
Field,
Scan,
Query
}
internal abstract class PathFilter
{
public abstract FilterType Type { get; }
}
internal class ArrayIndexFilter : PathFilter
{
public int? Index { get; set; }
public override FilterType Type
{
get { return FilterType.ArrayIndex; }
}
}
internal class ArrayMultipleIndexFilter : PathFilter
{
public List<int> Indexes { get; set; }
public override FilterType Type
{
get { return FilterType.ArrayMultipleIndex; }
}
}
internal class ArraySliceFilter : PathFilter
{
public int? Start { get; set; }
public int? End { get; set; }
public int? Skip { get; set; }
public override FilterType Type
{
get { return FilterType.ArraySlice; }
}
}
internal class FieldFilter : PathFilter
{
public string Name { get; set; }
public override FilterType Type
{
get { return FilterType.Field; }
}
}
internal class ScanFilter : PathFilter
{
public string Name { get; set; }
public override FilterType Type
{
get { return FilterType.Scan; }
}
}
internal class QueryFilter : PathFilter
{
public List<object> Expression { get; set; }
public override FilterType Type
{
get { return FilterType.Query; }
}
}
internal class JPath
{
private readonly string _expression;
public List<PathFilter> Parts { get; private set; }
public List<PathFilter> Filters { get; private set; }
private int _currentIndex;
@@ -120,7 +43,7 @@ namespace Newtonsoft.Json.Linq
{
ValidationUtils.ArgumentNotNull(expression, "expression");
_expression = expression;
Parts = new List<PathFilter>();
Filters = new List<PathFilter>();
ParseMain();
}
@@ -131,7 +54,10 @@ namespace Newtonsoft.Json.Linq
bool followingIndexer = false;
bool scan = false;
EnsureLength("Empty path.");
if (_expression.Length == 0)
return;
EatWhitespace();
if (_expression[_currentIndex] == '$')
{
@@ -140,7 +66,7 @@ namespace Newtonsoft.Json.Linq
EnsureLength("Unexpected end while parsing path.");
if (_expression[_currentIndex] != '.')
throw new JsonException("Unexpected character while parsing path indexer: " + _expression[_currentIndex]);
throw new JsonException("Unexpected character while parsing path: " + _expression[_currentIndex]);
_currentIndex++;
@@ -150,12 +76,9 @@ namespace Newtonsoft.Json.Linq
{
scan = true;
_currentIndex++;
currentPartStartIndex = 3;
}
else
{
currentPartStartIndex = 2;
}
currentPartStartIndex = _currentIndex;
}
while (_currentIndex < _expression.Length)
@@ -170,17 +93,23 @@ namespace Newtonsoft.Json.Linq
{
string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex);
PathFilter filter = (scan) ? (PathFilter)new ScanFilter() { Name = member } : new FieldFilter() { Name = member };
Parts.Add(filter);
Filters.Add(filter);
scan = false;
}
ParseIndexer(currentChar);
currentPartStartIndex = _currentIndex + 1;
_currentIndex++;
currentPartStartIndex = _currentIndex;
followingIndexer = true;
break;
case ']':
case ')':
throw new JsonException("Unexpected character while parsing path: " + currentChar);
case ' ':
EatWhitespace();
if (_currentIndex < _expression.Length)
throw new JsonException("Unexpected character while parsing path: " + currentChar);
break;
case '.':
if (_currentIndex > currentPartStartIndex)
{
@@ -188,7 +117,7 @@ namespace Newtonsoft.Json.Linq
if (member == "*")
member = null;
PathFilter filter = (scan) ? (PathFilter)new ScanFilter() { Name = member } : new FieldFilter() { Name = member };
Parts.Add(filter);
Filters.Add(filter);
scan = false;
}
if (_currentIndex + 1 < _expression.Length && _expression[_currentIndex + 1] == '.')
@@ -196,25 +125,26 @@ namespace Newtonsoft.Json.Linq
scan = true;
_currentIndex++;
}
currentPartStartIndex = _currentIndex + 1;
_currentIndex++;
currentPartStartIndex = _currentIndex;
followingIndexer = false;
break;
default:
if (followingIndexer)
throw new JsonException("Unexpected character following indexer: " + currentChar);
_currentIndex++;
break;
}
_currentIndex++;
}
if (_currentIndex > currentPartStartIndex)
{
string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex);
string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex).TrimEnd();
if (member == "*")
member = null;
PathFilter filter = (scan) ? (PathFilter)new ScanFilter() { Name = member } : new FieldFilter() { Name = member };
Parts.Add(filter);
Filters.Add(filter);
}
}
@@ -226,6 +156,8 @@ namespace Newtonsoft.Json.Linq
EnsureLength("Path ended with open indexer.");
EatWhitespace();
if (_expression[_currentIndex] == '\'')
{
ParseQuotedField(indexerCloseChar);
@@ -243,7 +175,9 @@ namespace Newtonsoft.Json.Linq
private void ParseArrayIndexer(char indexerCloseChar)
{
int start = _currentIndex;
int? end = null;
List<int> indexes = null;
int colonCount = 0;
int? startIndex = null;
int? endIndex = null;
int? step = null;
@@ -252,32 +186,58 @@ namespace Newtonsoft.Json.Linq
{
char currentCharacter = _expression[_currentIndex];
if (currentCharacter == ' ')
{
end = _currentIndex;
EatWhitespace();
continue;
}
if (currentCharacter == indexerCloseChar)
{
int length = _currentIndex - start;
if (length == 0)
throw new JsonException("Array index expected.");
string indexer = _expression.Substring(start, length);
int index = Convert.ToInt32(indexer, CultureInfo.InvariantCulture);
int length = (end ?? _currentIndex) - start;
if (indexes != null)
{
if (length == 0)
throw new JsonException("Array index expected.");
string indexer = _expression.Substring(start, length);
int index = Convert.ToInt32(indexer, CultureInfo.InvariantCulture);
indexes.Add(index);
Parts.Add(new ArrayMultipleIndexFilter { Indexes = indexes });
Filters.Add(new ArrayMultipleIndexFilter { Indexes = indexes });
}
else if (colonCount > 0)
{
if (length > 0)
{
string indexer = _expression.Substring(start, length);
int index = Convert.ToInt32(indexer, CultureInfo.InvariantCulture);
if (colonCount == 1)
endIndex = index;
else
step = index;
}
Filters.Add(new ArraySliceFilter { Start = startIndex, End = endIndex, Step = step });
}
else
{
Parts.Add(new ArrayIndexFilter { Index = index });
if (length == 0)
throw new JsonException("Array index expected.");
string indexer = _expression.Substring(start, length);
int index = Convert.ToInt32(indexer, CultureInfo.InvariantCulture);
Filters.Add(new ArrayIndexFilter { Index = index });
}
return;
}
if (currentCharacter == ',')
} else if (currentCharacter == ',')
{
int length = _currentIndex - start;
int length = (end ?? _currentIndex) - start;
if (length == 0)
throw new JsonException("Array index expected.");
@@ -288,30 +248,79 @@ namespace Newtonsoft.Json.Linq
string indexer = _expression.Substring(start, length);
indexes.Add(Convert.ToInt32(indexer, CultureInfo.InvariantCulture));
start = _currentIndex + 1;
_currentIndex++;
EatWhitespace();
start = _currentIndex;
end = null;
}
else if (currentCharacter == '*')
{
_currentIndex++;
EnsureLength("Path ended with open indexer.");
EatWhitespace();
if (_expression[_currentIndex] != indexerCloseChar)
throw new JsonException("Unexpected character while parsing path indexer: " + currentCharacter);
Parts.Add(new ArrayIndexFilter());
Filters.Add(new ArrayIndexFilter());
return;
}
else if (!char.IsDigit(currentCharacter))
else if (currentCharacter == ':')
{
int length = (end ?? _currentIndex) - start;
if (length > 0)
{
string indexer = _expression.Substring(start, length);
int index = Convert.ToInt32(indexer, CultureInfo.InvariantCulture);
if (colonCount == 0)
startIndex = index;
else if (colonCount == 1)
endIndex = index;
else
step = index;
}
colonCount++;
_currentIndex++;
EatWhitespace();
start = _currentIndex;
end = null;
}
else if (!char.IsDigit(currentCharacter) && currentCharacter != '-')
{
throw new JsonException("Unexpected character while parsing path indexer: " + currentCharacter);
}
else
{
if (end != null)
throw new JsonException("Unexpected character while parsing path indexer: " + currentCharacter);
_currentIndex++;
}
_currentIndex++;
}
throw new JsonException("Path ended with open indexer.");
}
private void EatWhitespace()
{
while (_currentIndex < _expression.Length)
{
if (_expression[_currentIndex] != ' ')
break;
_currentIndex++;
}
}
private void ParseQuery(char indexerCloseChar)
{
_currentIndex++;
@@ -334,7 +343,7 @@ namespace Newtonsoft.Json.Linq
if (_expression[_currentIndex] != indexerCloseChar)
throw new JsonException("Unexpected character while parsing path indexer: " + _expression[_currentIndex]);
Parts.Add(new QueryFilter
Filters.Add(new QueryFilter
{
Expression = expressions
});
@@ -383,11 +392,13 @@ namespace Newtonsoft.Json.Linq
// check that character after the quote is to close the index
_currentIndex++;
EnsureLength("Path ended with open indexer.");
EatWhitespace();
if (_expression[_currentIndex] != indexerCloseChar)
throw new JsonException("Unexpected character while parsing path indexer: " + currentCharacter);
string indexer = _expression.Substring(start, length);
Parts.Add(new FieldFilter { Name = indexer });
Filters.Add(new FieldFilter { Name = indexer });
return;
}
@@ -408,193 +419,12 @@ namespace Newtonsoft.Json.Linq
{
IEnumerable<JToken> current = new [] { root };
foreach (PathFilter filter in Parts)
foreach (PathFilter filter in Filters)
{
switch (filter.Type)
{
case FilterType.ArrayIndex:
current = ExecuteArrayIndexFilter(current, errorWhenNoMatch, (ArrayIndexFilter) filter);
break;
case FilterType.ArrayMultipleIndex:
current = ExecuteArrayMultipleIndexFilter(current, errorWhenNoMatch, (ArrayMultipleIndexFilter) filter);
break;
case FilterType.Field:
current = ExecuteFieldFilter(current, errorWhenNoMatch, (FieldFilter) filter);
break;
case FilterType.Scan:
current = ExecuteScanFilter(current, errorWhenNoMatch, (ScanFilter) filter);
break;
default:
throw new ArgumentOutOfRangeException();
}
current = filter.ExecuteFilter(current, errorWhenNoMatch);
}
return current;
}
private static IEnumerable<JToken> GetDescendants(IEnumerable<JToken> 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<JToken> ExecuteScanFilter(IEnumerable<JToken> current, bool errorWhenNoMatch, ScanFilter scanFilter)
{
return GetDescendants(current, scanFilter.Name);
}
private IEnumerable<JToken> ExecuteArrayMultipleIndexFilter(IEnumerable<JToken> current, bool errorWhenNoMatch, ArrayMultipleIndexFilter arrayMultipleIndexFilter)
{
foreach (JToken t in current)
{
foreach (int i in arrayMultipleIndexFilter.Indexes)
{
JToken v = GetTokenIndex(t, errorWhenNoMatch, i);
if (v != null)
yield return v;
}
}
}
private static IEnumerable<JToken> ExecuteFieldFilter(IEnumerable<JToken> current, bool errorWhenNoMatch, FieldFilter fieldFilter)
{
foreach (JToken t in current)
{
JObject o = t as JObject;
if (o != null)
{
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));
yield return v;
}
else
{
// todo - error when no results
foreach (KeyValuePair<string, JToken> 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));
}
}
}
private static IEnumerable<JToken> ExecuteArrayIndexFilter(IEnumerable<JToken> current, bool errorWhenNoMatch, ArrayIndexFilter arrayIndexFilter)
{
foreach (JToken t in current)
{
if (arrayIndexFilter.Index != null)
{
JToken v = GetTokenIndex(t, errorWhenNoMatch, arrayIndexFilter.Index.Value);
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));
}
}
}
}
private static JToken GetTokenIndex(JToken t, bool errorWhenNoMatch, int index)
{
JArray a = t as JArray;
JConstructor c = t as JConstructor;
if (a != null)
{
if (a.Count <= index)
{
if (errorWhenNoMatch)
throw new JsonException("Index {0} outside the bounds of JArray.".FormatWith(CultureInfo.InvariantCulture, index));
return null;
}
return a[index];
}
else if (c != null)
{
if (c.Count <= index)
{
if (errorWhenNoMatch)
throw new JsonException("Index {0} outside the bounds of JConstructor.".FormatWith(CultureInfo.InvariantCulture, index));
return null;
}
return c[index];
}
else
{
if (errorWhenNoMatch)
throw new JsonException("Index {0} not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, index, t.GetType().Name));
return null;
}
}
}
}
@@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json.Utilities;
namespace Newtonsoft.Json.Linq.JsonPath
{
internal abstract class PathFilter
{
public abstract IEnumerable<JToken> ExecuteFilter(IEnumerable<JToken> current, bool errorWhenNoMatch);
protected static JToken GetTokenIndex(JToken t, bool errorWhenNoMatch, int index)
{
JArray a = t as JArray;
JConstructor c = t as JConstructor;
if (a != null)
{
if (a.Count <= index)
{
if (errorWhenNoMatch)
throw new JsonException("Index {0} outside the bounds of JArray.".FormatWith(CultureInfo.InvariantCulture, index));
return null;
}
return a[index];
}
else if (c != null)
{
if (c.Count <= index)
{
if (errorWhenNoMatch)
throw new JsonException("Index {0} outside the bounds of JConstructor.".FormatWith(CultureInfo.InvariantCulture, index));
return null;
}
return c[index];
}
else
{
if (errorWhenNoMatch)
throw new JsonException("Index {0} not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, index, t.GetType().Name));
return null;
}
}
}
}
@@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Newtonsoft.Json.Linq.JsonPath
{
internal class QueryFilter : PathFilter
{
public List<object> Expression { get; set; }
public override IEnumerable<JToken> ExecuteFilter(IEnumerable<JToken> current, bool errorWhenNoMatch)
{
throw new System.NotImplementedException();
}
}
}
@@ -0,0 +1,55 @@
using System.Collections.Generic;
namespace Newtonsoft.Json.Linq.JsonPath
{
internal class ScanFilter : PathFilter
{
public string Name { get; set; }
public override IEnumerable<JToken> ExecuteFilter(IEnumerable<JToken> current, bool errorWhenNoMatch)
{
foreach (JToken root in current)
{
if (Name == null)
yield return root;
JToken value = root;
JToken container = root;
while (true)
{
if (container != null)
{
value = container.First;
}
else
{
while (value != null && value != root && value == value.Parent.Last)
{
value = value.Parent;
}
if (value == null || value == root)
break;
value = value.Next;
}
JProperty e = value as JProperty;
if (e != null)
{
if (e.Name == Name)
yield return e.Value;
}
else
{
if (Name == null)
yield return value;
}
container = value as JContainer;
}
}
}
}
}
+8 -1
View File
@@ -111,11 +111,18 @@
<Compile Include="Linq\JContainer.cs" />
<Compile Include="Linq\JEnumerable.cs" />
<Compile Include="Linq\JObject.cs" />
<Compile Include="Linq\JPath.cs" />
<Compile Include="Linq\JsonPath\ArrayIndexFilter.cs" />
<Compile Include="Linq\JsonPath\ArrayMultipleIndexFilter.cs" />
<Compile Include="Linq\JsonPath\ArraySliceFilter.cs" />
<Compile Include="Linq\JsonPath\FieldFilter.cs" />
<Compile Include="Linq\JsonPath\JPath.cs" />
<Compile Include="Linq\JProperty.cs" />
<Compile Include="Linq\JPropertyDescriptor.cs" />
<Compile Include="Linq\JPropertyKeyedCollection.cs" />
<Compile Include="Linq\JRaw.cs" />
<Compile Include="Linq\JsonPath\PathFilter.cs" />
<Compile Include="Linq\JsonPath\QueryFilter.cs" />
<Compile Include="Linq\JsonPath\ScanFilter.cs" />
<Compile Include="Linq\JToken.cs" />
<Compile Include="Linq\JTokenEqualityComparer.cs" />
<Compile Include="Linq\JTokenReader.cs" />