Add JsonSelectSettings and regex timeout
This commit is contained in:
@@ -22,7 +22,5 @@
|
||||
<SystemValueTuplePackageVersion>4.4.0</SystemValueTuplePackageVersion>
|
||||
<XunitPackageVersion>2.3.1</XunitPackageVersion>
|
||||
<XunitRunnerVisualStudioPackageVersion>2.3.1</XunitRunnerVisualStudioPackageVersion>
|
||||
<BogusPackageVersion>32.0.2</BogusPackageVersion>
|
||||
<AsyncExPackageVersion>5.1.0</AsyncExPackageVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -32,11 +32,7 @@ using System.Numerics;
|
||||
using Newtonsoft.Json.Linq.JsonPath;
|
||||
using Newtonsoft.Json.Tests.Bson;
|
||||
#if HAVE_REGEX_TIMEOUTS
|
||||
using Bogus;
|
||||
using Nito.AsyncEx;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
#endif
|
||||
#if DNXCORE50
|
||||
using Xunit;
|
||||
@@ -81,69 +77,21 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
|
||||
[Test]
|
||||
public void BacktrackingRegex_SingleMatch_TimeoutRespected()
|
||||
{
|
||||
var RegexBacktrackingPattern = "(?<a>(.*?))[|].*(?<b>(.*?))[|].*(?<c>(.*?))[|].*(?<d>[1-3])[|].*(?<e>(.*?))[|].*[|].*[|].*(?<f>(.*?))[|].*[|].*(?<g>(.*?))[|].*(?<h>(.*))";
|
||||
const string RegexBacktrackingPattern = "(?<a>(.*?))[|].*(?<b>(.*?))[|].*(?<c>(.*?))[|].*(?<d>[1-3])[|].*(?<e>(.*?))[|].*[|].*[|].*(?<f>(.*?))[|].*[|].*(?<g>(.*?))[|].*(?<h>(.*))";
|
||||
|
||||
var faker = new Faker();
|
||||
var regexBacktrackingData = new JArray();
|
||||
regexBacktrackingData.Add(new JObject(new JProperty("b", @"15/04/2020 8:18:03 PM|1|System.String[]|3|Libero eligendi magnam ut inventore.. Quaerat et sit voluptatibus repellendus blanditiis aliquam ut.. Quidem qui ut sint in ex et tempore.|||.\iste.cpp||46018|-1")));
|
||||
|
||||
for (var i = 0; i < 1000; i++)
|
||||
ExceptionAssert.Throws<RegexMatchTimeoutException>(() =>
|
||||
{
|
||||
var value = $"{faker.Date.Past()}|1|{faker.Lorem.Words()}|3|{faker.Lorem.Sentences(3, ". ")}|||.\\{faker.Lorem.Word()}.cpp||{faker.Random.UShort()}|-1";
|
||||
regexBacktrackingData.Add(new JObject(new JProperty("b", value)));
|
||||
}
|
||||
|
||||
Xunit.Assert.Throws<RegexMatchTimeoutException>(() =>
|
||||
{
|
||||
var tokens = regexBacktrackingData.SelectTokens(
|
||||
regexBacktrackingData.SelectTokens(
|
||||
$"[?(@.b =~ /{RegexBacktrackingPattern}/)]",
|
||||
errorWhenNoMatch: false,
|
||||
singleRegexMatchTimeout: TimeSpan.FromSeconds(.01)).ToArray();
|
||||
new JsonSelectSettings
|
||||
{
|
||||
RegexMatchTimeout = TimeSpan.FromSeconds(0.01)
|
||||
}).ToArray();
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BacktrackingRegex_GlobalMatch_TimeoutRespected()
|
||||
{
|
||||
var faker = new Faker();
|
||||
var regexBacktrackingData = new JArray();
|
||||
for (var i = 0; i < 1000; i++)
|
||||
{
|
||||
var value = $"{faker.Date.Past()}|1|{faker.Lorem.Words()}|3|{faker.Lorem.Sentences(3, ". ")}|||.\\{faker.Lorem.Word()}.cpp||{faker.Random.UShort()}|-1";
|
||||
regexBacktrackingData.Add(new JObject(new JProperty("b", value)));
|
||||
}
|
||||
var RegexBacktrackingPattern = "(?<i>(.*?))[|].*(?<b>(.*?))[|].*(?<c>(.*?))[|].*(?<d>[1-3])[|].*(?<e>(.*?))[|].*[|].*[|].*(?<f>(.*?))[|].*[|].*(?<g>(.*?))[|].*(?<h>(.*))";
|
||||
var jpathExpression = $"[?(@.b =~ /{RegexBacktrackingPattern}/c)]";
|
||||
|
||||
var exceptionThrow = Xunit.Assert.ThrowsAny<Exception>(() =>
|
||||
{
|
||||
var tokens = regexBacktrackingData.SelectTokens(
|
||||
jpathExpression,
|
||||
errorWhenNoMatch: false,
|
||||
singleRegexMatchTimeout: TimeSpan.FromSeconds(2),
|
||||
globalRegexMatchTimeout: TimeSpan.FromSeconds(.5)).ToArray();
|
||||
});
|
||||
Assert.IsTrue(exceptionThrow is OperationCanceledException or RegexMatchTimeoutException);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task BacktrackingRegexCanBeVerySlowWithoutTimeouts()
|
||||
{
|
||||
var RegexBacktrackingPattern = "(?<j>(.*?))[|].*(?<b>(.*?))[|].*(?<c>(.*?))[|].*(?<d>[1-3])[|].*(?<e>(.*?))[|].*[|].*[|].*(?<f>(.*?))[|].*[|].*(?<g>(.*?))[|].*(?<h>(.*))";
|
||||
var faker = new Faker();
|
||||
var regexBacktrackingData = new JArray();
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
var value = $"{faker.Date.Past()}|1|{faker.Lorem.Words()}|3|{faker.Lorem.Sentences(3, ". ")}|||.\\{faker.Lorem.Word()}.cpp||{faker.Random.UShort()}|-1";
|
||||
regexBacktrackingData.Add(new JObject(new JProperty("b", value)));
|
||||
}
|
||||
|
||||
var selectTokenTask = Task.Run(() => regexBacktrackingData.SelectTokens($"[?(@.b =~ /{RegexBacktrackingPattern}/)]").ToArray());
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(.3));
|
||||
using var ctts = new CancellationTokenTaskSource<bool>(cts.Token);
|
||||
var finishedTask = await Task.WhenAny(selectTokenTask, ctts.Task);
|
||||
Assert.AreEqual(finishedTask, ctts.Task);
|
||||
}
|
||||
#endif
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net46'">
|
||||
<AssemblyTitle>Json.NET Tests</AssemblyTitle>
|
||||
<ReferringTargetFrameworkForProjectReferences>.NETFramework,Version=v4.5</ReferringTargetFrameworkForProjectReferences>
|
||||
<DefineConstants>NET45;HAVE_BENCHMARKS;$(AdditionalConstants)</DefineConstants>
|
||||
<DefineConstants>NET45;HAVE_BENCHMARKS;HAVE_REGEX_TIMEOUTS;$(AdditionalConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net40'">
|
||||
@@ -76,7 +76,6 @@
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System.Web.Extensions" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<PackageReference Include="Bogus" Version="$(BogusPackageVersion)" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net40'">
|
||||
<AssemblyTitle>Json.NET Tests .NET 4.0</AssemblyTitle>
|
||||
@@ -122,10 +121,7 @@
|
||||
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
|
||||
<PackageReference Include="Bogus" Version="$(BogusPackageVersion)" />
|
||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="$(AsyncExPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net5.0'">
|
||||
<AssemblyTitle>Json.NET Tests .NET Standard 2.0</AssemblyTitle>
|
||||
<ReferringTargetFrameworkForProjectReferences>.NETStandard,Version=v2.0</ReferringTargetFrameworkForProjectReferences>
|
||||
@@ -146,8 +142,6 @@
|
||||
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
|
||||
<PackageReference Include="Bogus" Version="$(BogusPackageVersion)" />
|
||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="$(AsyncExPackageVersion)" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='netcoreapp3.1'">
|
||||
<AssemblyTitle>Json.NET Tests .NET Standard 1.3</AssemblyTitle>
|
||||
@@ -168,10 +162,7 @@
|
||||
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
|
||||
<PackageReference Include="Bogus" Version="$(BogusPackageVersion)" />
|
||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="$(AsyncExPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='netcoreapp2.1'">
|
||||
<AssemblyTitle>Json.NET Tests .NET Standard 1.0</AssemblyTitle>
|
||||
<ReferringTargetFrameworkForProjectReferences>.NETStandard,Version=v1.0</ReferringTargetFrameworkForProjectReferences>
|
||||
|
||||
@@ -44,7 +44,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json.Utilities.LinqBridge;
|
||||
#else
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
#endif
|
||||
|
||||
namespace Newtonsoft.Json.Linq
|
||||
@@ -2308,7 +2307,7 @@ namespace Newtonsoft.Json.Linq
|
||||
/// <returns>A <see cref="JToken"/>, or <c>null</c>.</returns>
|
||||
public JToken? SelectToken(string path)
|
||||
{
|
||||
return SelectToken(path, false);
|
||||
return SelectToken(path, settings: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -2321,38 +2320,27 @@ namespace Newtonsoft.Json.Linq
|
||||
/// <returns>A <see cref="JToken"/>.</returns>
|
||||
public JToken? SelectToken(string path, bool errorWhenNoMatch)
|
||||
{
|
||||
JPath p = new JPath(path);
|
||||
JsonSelectSettings? settings = errorWhenNoMatch
|
||||
? new JsonSelectSettings { ErrorWhenNoMatch = true }
|
||||
: null;
|
||||
|
||||
JToken? token = null;
|
||||
foreach (JToken t in p.Evaluate(this, this, errorWhenNoMatch))
|
||||
{
|
||||
if (token != null)
|
||||
{
|
||||
throw new JsonException("Path returned multiple tokens.");
|
||||
}
|
||||
|
||||
token = t;
|
||||
}
|
||||
|
||||
return token;
|
||||
return SelectToken(path, settings);
|
||||
}
|
||||
|
||||
#if HAVE_REGEX_TIMEOUTS
|
||||
/// <summary>
|
||||
/// Selects a <see cref="JToken"/> using a JSONPath expression. Selects the token that matches the object path.
|
||||
/// </summary>
|
||||
/// <param name="path">
|
||||
/// A <see cref="String"/> that contains a JSONPath expression.
|
||||
/// </param>
|
||||
/// <param name="errorWhenNoMatch">A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression.</param>
|
||||
/// <param name="singleMatchTimeout">the time after which a single call to regex.ismatch must complete, default is forever</param>
|
||||
/// <param name="settings">The <see cref="JsonSelectSettings"/> used to select tokens.</param>
|
||||
/// <returns>A <see cref="JToken"/>.</returns>
|
||||
public JToken? SelectToken(string path, bool errorWhenNoMatch, TimeSpan? singleMatchTimeout = default)
|
||||
public JToken? SelectToken(string path, JsonSelectSettings? settings)
|
||||
{
|
||||
JPath p = new JPath(path, singleMatchTimeout);
|
||||
JPath p = new JPath(path);
|
||||
|
||||
JToken? token = null;
|
||||
foreach (JToken t in p.Evaluate(this, this, errorWhenNoMatch))
|
||||
foreach (JToken t in p.Evaluate(this, this, settings))
|
||||
{
|
||||
if (token != null)
|
||||
{
|
||||
@@ -2364,7 +2352,6 @@ namespace Newtonsoft.Json.Linq
|
||||
|
||||
return token;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Selects a collection of elements using a JSONPath expression.
|
||||
@@ -2375,7 +2362,7 @@ namespace Newtonsoft.Json.Linq
|
||||
/// <returns>An <see cref="IEnumerable{T}"/> of <see cref="JToken"/> that contains the selected elements.</returns>
|
||||
public IEnumerable<JToken> SelectTokens(string path)
|
||||
{
|
||||
return SelectTokens(path, false);
|
||||
return SelectTokens(path, settings: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -2388,50 +2375,26 @@ namespace Newtonsoft.Json.Linq
|
||||
/// <returns>An <see cref="IEnumerable{T}"/> of <see cref="JToken"/> that contains the selected elements.</returns>
|
||||
public IEnumerable<JToken> SelectTokens(string path, bool errorWhenNoMatch)
|
||||
{
|
||||
var p = new JPath(path);
|
||||
return p.Evaluate(this, this, errorWhenNoMatch);
|
||||
JsonSelectSettings? settings = errorWhenNoMatch
|
||||
? new JsonSelectSettings { ErrorWhenNoMatch = true }
|
||||
: null;
|
||||
|
||||
return SelectTokens(path, settings);
|
||||
}
|
||||
|
||||
#if HAVE_REGEX_TIMEOUTS
|
||||
/// <summary>
|
||||
/// Selects a collection of elements using a JSONPath expression.
|
||||
/// </summary>
|
||||
/// <param name="path">
|
||||
/// A <see cref="String"/> that contains a JSONPath expression.
|
||||
/// </param>
|
||||
/// <param name="errorWhenNoMatch">A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression.</param>
|
||||
/// <param name="singleRegexMatchTimeout">
|
||||
/// for every token that matches a jpath, the time a regex is permitted to run
|
||||
/// against that token before timeout
|
||||
/// </param>
|
||||
/// <param name="globalRegexMatchTimeout">
|
||||
/// the time this method should wait for the given jpath regex to match all tokens.
|
||||
/// worst case expected execution time roughly globalRegexMatchTimeout + Min(singleRegexMatchTimeout, globalRegexMatchTimeout)
|
||||
/// </param>
|
||||
/// <exception cref="System.Text.RegularExpressions.RegexMatchTimeoutException">if single call timeout exceeded</exception>"
|
||||
/// <param name="settings">The <see cref="JsonSelectSettings"/> used to select tokens.</param>
|
||||
/// <returns>An <see cref="IEnumerable{T}"/> of <see cref="JToken"/> that contains the selected elements.</returns>
|
||||
public IEnumerable<JToken> SelectTokens(string path,
|
||||
bool errorWhenNoMatch,
|
||||
TimeSpan? singleRegexMatchTimeout = default,
|
||||
TimeSpan? globalRegexMatchTimeout = default)
|
||||
public IEnumerable<JToken> SelectTokens(string path, JsonSelectSettings? settings)
|
||||
{
|
||||
var singleTimeout = singleRegexMatchTimeout ?? Timeout.InfiniteTimeSpan;
|
||||
var globalTimeout = globalRegexMatchTimeout ?? Timeout.InfiniteTimeSpan;
|
||||
if (globalTimeout != Timeout.InfiniteTimeSpan && globalTimeout < singleTimeout)
|
||||
{
|
||||
singleTimeout = globalTimeout;
|
||||
}
|
||||
|
||||
using var cts = new CancellationTokenSource(globalTimeout);
|
||||
var p = new JPath(path, singleTimeout);
|
||||
var results = p.Evaluate(this, this, errorWhenNoMatch);
|
||||
foreach (var result in results)
|
||||
{
|
||||
cts.Token.ThrowIfCancellationRequested();
|
||||
yield return result;
|
||||
}
|
||||
var p = new JPath(path);
|
||||
return p.Evaluate(this, this, settings);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAVE_DYNAMIC
|
||||
/// <summary>
|
||||
|
||||
@@ -8,13 +8,13 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
{
|
||||
public int? Index { get; set; }
|
||||
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
|
||||
{
|
||||
foreach (JToken t in current)
|
||||
{
|
||||
if (Index != null)
|
||||
{
|
||||
JToken? v = GetTokenIndex(t, errorWhenNoMatch, Index.GetValueOrDefault());
|
||||
JToken? v = GetTokenIndex(t, settings, Index.GetValueOrDefault());
|
||||
|
||||
if (v != null)
|
||||
{
|
||||
@@ -32,7 +32,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
}
|
||||
else
|
||||
{
|
||||
if (errorWhenNoMatch)
|
||||
if (settings?.ErrorWhenNoMatch ?? false)
|
||||
{
|
||||
throw new JsonException("Index * not valid on {0}.".FormatWith(CultureInfo.InvariantCulture, t.GetType().Name));
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
Indexes = indexes;
|
||||
}
|
||||
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
|
||||
{
|
||||
foreach (JToken t in current)
|
||||
{
|
||||
foreach (int i in Indexes)
|
||||
{
|
||||
JToken? v = GetTokenIndex(t, errorWhenNoMatch, i);
|
||||
JToken? v = GetTokenIndex(t, settings, i);
|
||||
|
||||
if (v != null)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
public int? End { get; set; }
|
||||
public int? Step { get; set; }
|
||||
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
|
||||
{
|
||||
if (Step == 0)
|
||||
{
|
||||
@@ -56,7 +56,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
}
|
||||
else
|
||||
{
|
||||
if (errorWhenNoMatch)
|
||||
if (settings?.ErrorWhenNoMatch ?? false)
|
||||
{
|
||||
throw new JsonException("Array slice of {0} to {1} returned no results.".FormatWith(CultureInfo.InvariantCulture,
|
||||
Start != null ? Start.GetValueOrDefault().ToString(CultureInfo.InvariantCulture) : "*",
|
||||
@@ -66,7 +66,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
}
|
||||
else
|
||||
{
|
||||
if (errorWhenNoMatch)
|
||||
if (settings?.ErrorWhenNoMatch ?? false)
|
||||
{
|
||||
throw new JsonException("Array slice is not valid on {0}.".FormatWith(CultureInfo.InvariantCulture, t.GetType().Name));
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
|
||||
{
|
||||
foreach (JToken t in current)
|
||||
{
|
||||
@@ -27,7 +27,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
{
|
||||
yield return v;
|
||||
}
|
||||
else if (errorWhenNoMatch)
|
||||
else if (settings?.ErrorWhenNoMatch ?? false)
|
||||
{
|
||||
throw new JsonException("Property '{0}' does not exist on JObject.".FormatWith(CultureInfo.InvariantCulture, Name));
|
||||
}
|
||||
@@ -42,7 +42,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
}
|
||||
else
|
||||
{
|
||||
if (errorWhenNoMatch)
|
||||
if (settings?.ErrorWhenNoMatch ?? false)
|
||||
{
|
||||
throw new JsonException("Property '{0}' not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, Name ?? "*", t.GetType().Name));
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
Names = names;
|
||||
}
|
||||
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
|
||||
{
|
||||
foreach (JToken t in current)
|
||||
{
|
||||
@@ -33,7 +33,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
yield return v;
|
||||
}
|
||||
|
||||
if (errorWhenNoMatch)
|
||||
if (settings?.ErrorWhenNoMatch ?? false)
|
||||
{
|
||||
throw new JsonException("Property '{0}' does not exist on JObject.".FormatWith(CultureInfo.InvariantCulture, name));
|
||||
}
|
||||
@@ -41,7 +41,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
}
|
||||
else
|
||||
{
|
||||
if (errorWhenNoMatch)
|
||||
if (settings?.ErrorWhenNoMatch ?? false)
|
||||
{
|
||||
throw new JsonException("Properties {0} not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, string.Join(", ", Names.Select(n => "'" + n + "'")
|
||||
#if !HAVE_STRING_JOIN_WITH_ENUMERABLE
|
||||
|
||||
@@ -27,8 +27,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using Newtonsoft.Json.Utilities;
|
||||
|
||||
namespace Newtonsoft.Json.Linq.JsonPath
|
||||
@@ -39,16 +37,14 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
|
||||
private readonly string _expression;
|
||||
public List<PathFilter> Filters { get; }
|
||||
public readonly TimeSpan? RegexSingleMatchTimeout = default;
|
||||
|
||||
private int _currentIndex;
|
||||
|
||||
public JPath(string expression, TimeSpan? regexSingleMatchTimeout = default)
|
||||
public JPath(string expression)
|
||||
{
|
||||
ValidationUtils.ArgumentNotNull(expression, nameof(expression));
|
||||
_expression = expression;
|
||||
Filters = new List<PathFilter>();
|
||||
RegexSingleMatchTimeout = regexSingleMatchTimeout;
|
||||
|
||||
ParseMain();
|
||||
}
|
||||
@@ -513,8 +509,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
right = ParseSide();
|
||||
}
|
||||
|
||||
|
||||
var booleanExpression = new BooleanQueryExpression(op, left, right, RegexSingleMatchTimeout);
|
||||
BooleanQueryExpression booleanExpression = new BooleanQueryExpression(op, left, right);
|
||||
|
||||
if (_expression[_currentIndex] == ')')
|
||||
{
|
||||
@@ -879,17 +874,17 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
}
|
||||
}
|
||||
|
||||
internal IEnumerable<JToken> Evaluate(JToken root, JToken t, bool errorWhenNoMatch)
|
||||
internal IEnumerable<JToken> Evaluate(JToken root, JToken t, JsonSelectSettings? settings)
|
||||
{
|
||||
return Evaluate(Filters, root, t, errorWhenNoMatch);
|
||||
return Evaluate(Filters, root, t, settings);
|
||||
}
|
||||
|
||||
internal static IEnumerable<JToken> Evaluate(List<PathFilter> filters, JToken root, JToken t, bool errorWhenNoMatch)
|
||||
internal static IEnumerable<JToken> Evaluate(List<PathFilter> filters, JToken root, JToken t, JsonSelectSettings? settings)
|
||||
{
|
||||
IEnumerable<JToken> current = new[] { t };
|
||||
foreach (PathFilter filter in filters)
|
||||
{
|
||||
current = filter.ExecuteFilter(root, current, errorWhenNoMatch);
|
||||
current = filter.ExecuteFilter(root, current, settings);
|
||||
}
|
||||
|
||||
return current;
|
||||
|
||||
@@ -6,15 +6,15 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
{
|
||||
internal abstract class PathFilter
|
||||
{
|
||||
public abstract IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch);
|
||||
public abstract IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings);
|
||||
|
||||
protected static JToken? GetTokenIndex(JToken t, bool errorWhenNoMatch, int index)
|
||||
protected static JToken? GetTokenIndex(JToken t, JsonSelectSettings? settings, int index)
|
||||
{
|
||||
if (t is JArray a)
|
||||
{
|
||||
if (a.Count <= index)
|
||||
{
|
||||
if (errorWhenNoMatch)
|
||||
if (settings?.ErrorWhenNoMatch ?? false)
|
||||
{
|
||||
throw new JsonException("Index {0} outside the bounds of JArray.".FormatWith(CultureInfo.InvariantCulture, index));
|
||||
}
|
||||
@@ -28,7 +28,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
{
|
||||
if (c.Count <= index)
|
||||
{
|
||||
if (errorWhenNoMatch)
|
||||
if (settings?.ErrorWhenNoMatch ?? false)
|
||||
{
|
||||
throw new JsonException("Index {0} outside the bounds of JConstructor.".FormatWith(CultureInfo.InvariantCulture, index));
|
||||
}
|
||||
@@ -40,7 +40,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
}
|
||||
else
|
||||
{
|
||||
if (errorWhenNoMatch)
|
||||
if (settings?.ErrorWhenNoMatch ?? false)
|
||||
{
|
||||
throw new JsonException("Index {0} not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, index, t.GetType().Name));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
#if !HAVE_LINQ
|
||||
using Newtonsoft.Json.Utilities.LinqBridge;
|
||||
#else
|
||||
@@ -40,7 +39,13 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
Operator = @operator;
|
||||
}
|
||||
|
||||
public abstract bool IsMatch(JToken root, JToken t);
|
||||
// For unit tests
|
||||
public bool IsMatch(JToken root, JToken t)
|
||||
{
|
||||
return IsMatch(root, t, null);
|
||||
}
|
||||
|
||||
public abstract bool IsMatch(JToken root, JToken t, JsonSelectSettings? settings);
|
||||
}
|
||||
|
||||
internal class CompositeExpression : QueryExpression
|
||||
@@ -52,14 +57,14 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
Expressions = new List<QueryExpression>();
|
||||
}
|
||||
|
||||
public override bool IsMatch(JToken root, JToken t)
|
||||
public override bool IsMatch(JToken root, JToken t, JsonSelectSettings? settings)
|
||||
{
|
||||
switch (Operator)
|
||||
{
|
||||
case QueryOperator.And:
|
||||
foreach (QueryExpression e in Expressions)
|
||||
{
|
||||
if (!e.IsMatch(root, t))
|
||||
if (!e.IsMatch(root, t, settings))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -68,7 +73,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
case QueryOperator.Or:
|
||||
foreach (QueryExpression e in Expressions)
|
||||
{
|
||||
if (e.IsMatch(root, t))
|
||||
if (e.IsMatch(root, t, settings))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -84,17 +89,11 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
{
|
||||
public readonly object Left;
|
||||
public readonly object? Right;
|
||||
public readonly TimeSpan? SingleMatchTimeout;
|
||||
|
||||
public BooleanQueryExpression(QueryOperator @operator,
|
||||
object left,
|
||||
object? right,
|
||||
TimeSpan? regexSingleMatchTimeout = default)
|
||||
: base(@operator)
|
||||
public BooleanQueryExpression(QueryOperator @operator, object left, object? right) : base(@operator)
|
||||
{
|
||||
Left = left;
|
||||
Right = right;
|
||||
SingleMatchTimeout = regexSingleMatchTimeout;
|
||||
}
|
||||
|
||||
private IEnumerable<JToken> GetResult(JToken root, JToken t, object? o)
|
||||
@@ -106,13 +105,13 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
|
||||
if (o is List<PathFilter> pathFilters)
|
||||
{
|
||||
return JPath.Evaluate(pathFilters, root, t, false);
|
||||
return JPath.Evaluate(pathFilters, root, t, null);
|
||||
}
|
||||
|
||||
return CollectionUtils.ArrayEmpty<JToken>();
|
||||
}
|
||||
|
||||
public override bool IsMatch(JToken root, JToken t)
|
||||
public override bool IsMatch(JToken root, JToken t, JsonSelectSettings? settings)
|
||||
{
|
||||
if (Operator == QueryOperator.Exists)
|
||||
{
|
||||
@@ -131,7 +130,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
JToken leftResult = leftResults.Current;
|
||||
foreach (JToken rightResult in rightResults)
|
||||
{
|
||||
if (MatchTokens(leftResult, rightResult))
|
||||
if (MatchTokens(leftResult, rightResult, settings))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -143,14 +142,14 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool MatchTokens(JToken leftResult, JToken rightResult)
|
||||
private bool MatchTokens(JToken leftResult, JToken rightResult, JsonSelectSettings? settings)
|
||||
{
|
||||
if (leftResult is JValue leftValue && rightResult is JValue rightValue)
|
||||
{
|
||||
switch (Operator)
|
||||
{
|
||||
case QueryOperator.RegexEquals:
|
||||
if (RegexEquals(leftValue, rightValue, SingleMatchTimeout))
|
||||
if (RegexEquals(leftValue, rightValue, settings))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -222,9 +221,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool RegexEquals(JValue input,
|
||||
JValue pattern,
|
||||
TimeSpan? regexMatchTimeout = default)
|
||||
private static bool RegexEquals(JValue input, JValue pattern, JsonSelectSettings? settings)
|
||||
{
|
||||
if (input.Type != JTokenType.String || pattern.Type != JTokenType.String)
|
||||
{
|
||||
@@ -238,7 +235,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
string optionsText = regexText.Substring(patternOptionDelimiterIndex + 1);
|
||||
|
||||
#if HAVE_REGEX_TIMEOUTS
|
||||
var timeout = regexMatchTimeout ?? Regex.InfiniteMatchTimeout;
|
||||
TimeSpan timeout = settings?.RegexMatchTimeout ?? Regex.InfiniteMatchTimeout;
|
||||
return Regex.IsMatch((string)input.Value!, patternText, MiscellaneousUtils.GetRegexOptions(optionsText), timeout);
|
||||
#else
|
||||
return Regex.IsMatch((string)input.Value!, patternText, MiscellaneousUtils.GetRegexOptions(optionsText));
|
||||
|
||||
@@ -12,13 +12,13 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
Expression = expression;
|
||||
}
|
||||
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
|
||||
{
|
||||
foreach (JToken t in current)
|
||||
{
|
||||
foreach (JToken v in t)
|
||||
{
|
||||
if (Expression.IsMatch(root, v))
|
||||
if (Expression.IsMatch(root, v, settings))
|
||||
{
|
||||
yield return v;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
Expression = expression;
|
||||
}
|
||||
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
|
||||
{
|
||||
foreach (JToken t in current)
|
||||
{
|
||||
@@ -20,7 +20,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
{
|
||||
foreach (JToken d in c.DescendantsAndSelf())
|
||||
{
|
||||
if (Expression.IsMatch(root, d))
|
||||
if (Expression.IsMatch(root, d, settings))
|
||||
{
|
||||
yield return d;
|
||||
}
|
||||
@@ -28,7 +28,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Expression.IsMatch(root, t))
|
||||
if (Expression.IsMatch(root, t, settings))
|
||||
{
|
||||
yield return t;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
|
||||
{
|
||||
return new[] { root };
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
|
||||
{
|
||||
foreach (JToken c in current)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
_names = names;
|
||||
}
|
||||
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
|
||||
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
|
||||
{
|
||||
foreach (JToken c in current)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace Newtonsoft.Json.Linq
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the settings used when selecting JSON.
|
||||
/// </summary>
|
||||
public class JsonSelectSettings
|
||||
{
|
||||
#if HAVE_REGEX_TIMEOUTS
|
||||
/// <summary>
|
||||
/// Gets or sets a timeout that will be used when executing regular expressions.
|
||||
/// </summary>
|
||||
/// <value>The timeout that will be used when executing regular expressions.</value>
|
||||
public TimeSpan? RegexMatchTimeout { get; set; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag that indicates whether an error should be thrown if
|
||||
/// no tokens are found when evaluating part of the expression.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A flag that indicates whether an error should be thrown if
|
||||
/// no tokens are found when evaluating part of the expression.
|
||||
/// </value>
|
||||
public bool ErrorWhenNoMatch { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net45'">
|
||||
<AssemblyTitle>Json.NET</AssemblyTitle>
|
||||
<DefineConstants>HAVE_ADO_NET;HAVE_APP_DOMAIN;HAVE_ASYNC;HAVE_BIG_INTEGER;HAVE_BINARY_FORMATTER;HAVE_BINARY_SERIALIZATION;HAVE_BINARY_EXCEPTION_SERIALIZATION;HAVE_CAS;HAVE_CHAR_TO_LOWER_WITH_CULTURE;HAVE_CHAR_TO_STRING_WITH_CULTURE;HAVE_COM_ATTRIBUTES;HAVE_COMPONENT_MODEL;HAVE_CONCURRENT_COLLECTIONS;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DB_NULL_TYPE_CODE;HAVE_DYNAMIC;HAVE_EMPTY_TYPES;HAVE_ENTITY_FRAMEWORK;HAVE_EXPRESSIONS;HAVE_FAST_REVERSE;HAVE_FSHARP_TYPES;HAVE_FULL_REFLECTION;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_ICLONEABLE;HAVE_ICONVERTIBLE;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_INOTIFY_PROPERTY_CHANGING;HAVE_ISET;HAVE_LINQ;HAVE_MEMORY_BARRIER;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_NON_SERIALIZED_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_REFLECTION_EMIT;HAVE_SECURITY_SAFE_CRITICAL_ATTRIBUTE;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STREAM_READER_WRITER_CLOSE;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TRACE_WRITER;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_XML_DOCUMENT;HAVE_XML_DOCUMENT_TYPE;HAVE_CONCURRENT_DICTIONARY;$(AdditionalConstants)</DefineConstants>
|
||||
<DefineConstants>HAVE_ADO_NET;HAVE_APP_DOMAIN;HAVE_ASYNC;HAVE_BIG_INTEGER;HAVE_BINARY_FORMATTER;HAVE_BINARY_SERIALIZATION;HAVE_BINARY_EXCEPTION_SERIALIZATION;HAVE_CAS;HAVE_CHAR_TO_LOWER_WITH_CULTURE;HAVE_CHAR_TO_STRING_WITH_CULTURE;HAVE_COM_ATTRIBUTES;HAVE_COMPONENT_MODEL;HAVE_CONCURRENT_COLLECTIONS;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DB_NULL_TYPE_CODE;HAVE_DYNAMIC;HAVE_EMPTY_TYPES;HAVE_ENTITY_FRAMEWORK;HAVE_EXPRESSIONS;HAVE_FAST_REVERSE;HAVE_FSHARP_TYPES;HAVE_FULL_REFLECTION;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_ICLONEABLE;HAVE_ICONVERTIBLE;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_INOTIFY_PROPERTY_CHANGING;HAVE_ISET;HAVE_LINQ;HAVE_MEMORY_BARRIER;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_NON_SERIALIZED_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_REFLECTION_EMIT;HAVE_REGEX_TIMEOUTS;HAVE_SECURITY_SAFE_CRITICAL_ATTRIBUTE;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STREAM_READER_WRITER_CLOSE;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TRACE_WRITER;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_XML_DOCUMENT;HAVE_XML_DOCUMENT_TYPE;HAVE_CONCURRENT_DICTIONARY;$(AdditionalConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net40'">
|
||||
<AssemblyTitle>Json.NET .NET 4.0</AssemblyTitle>
|
||||
|
||||
@@ -165,11 +165,6 @@ namespace Newtonsoft.Json.Utilities
|
||||
case 'x':
|
||||
options |= RegexOptions.ExplicitCapture;
|
||||
break;
|
||||
#if HAVE_REGEX_TIMEOUTS && (NETSTANDARD1_3 || NETSTANDARD2_0)
|
||||
case 'c':
|
||||
options |= RegexOptions.Compiled;
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user