Skip to content

Commit

Permalink
Refactor filter usages
Browse files Browse the repository at this point in the history
  • Loading branch information
bfarmer67 committed Jun 10, 2024
1 parent 86ef63c commit b80b917
Show file tree
Hide file tree
Showing 27 changed files with 247 additions and 387 deletions.
25 changes: 18 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A C# implementation of JSONPath for .NET `System.Text.Json` and `System.Text.Jso

.NET `System.Text.Json` lacks support for JSONPath. The primary goal of this project is to provide a JSONPath library for .NET that will

* Directly leverage `System.Text.Json`
* Directly leverage `System.Text.Json` and `System.Text.Json.Nodes`
* Align with the draft JSONPath Specification RFC 9535
* [Working Draft](https://github.com/ietf-wg-jsonpath/draft-ietf-jsonpath-base).
* [Editor Copy](https://ietf-wg-jsonpath.github.io/draft-ietf-jsonpath-base/draft-ietf-jsonpath-base.html)
Expand Down Expand Up @@ -36,8 +36,7 @@ JSONPath allows the wildcard symbol `*` for member names and array indices. It
borrows the descendant operator `..` from [E4X][e4x] and the array slice
syntax proposal `[start:end:step]` from ECMASCRIPT 4.

Expressions of the underlying scripting language (`<expr>`) can be used as an
alternative to explicit names or indices, as in:
Expressions can be used as an alternative to explicit names or indices, as in:

$.store.book[(@.length-1)].title

Expand Down Expand Up @@ -197,12 +196,24 @@ the `TryReadValueHandler` on the converter. This handler will allow you to inter
numeric values during the deserialization process.

### Equality Helpers
* `JsonElement.DeepEquals`
* `JsonElementEqualityDeepComparer`
* `JsonElementPositionComparer`

| Method | Description
|:-----------------------------------|:-----------
| `JsonElement.DeepEquals` | Performs a deep equals comparison
| `JsonElementEqualityDeepComparer` | A deep equals equality comparer
| `JsonElementPositionComparer` | A position comparer that compares position in the backing stream

### Property Diving
* `JsonElement.GetPropertyFromKey`

| Method | Description
|:-----------------------------------|:-----------
| `JsonElement.GetPropertyFromKey` | Dives for properties using absolute keys like `$['store']['book'][2]['author']`

### JsonElement Helpers

| Method | Description
|:-----------------------------------|:-----------
| `JsonPathBuilder` | Returns the absolute JsonPath string for a given element

## Acknowlegements

Expand Down
4 changes: 1 addition & 3 deletions src/Hyperbee.Json/Evaluators/IJsonPathFilterEvaluator.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@

namespace Hyperbee.Json.Evaluators;

public delegate object JsonPathEvaluator<in TType>( string filter, TType current, TType root );

public interface IJsonPathFilterEvaluator<in TType>
{
public object Evaluator( string filter, TType current, TType root );
public object Evaluate( string filter, TType current, TType root );
}
10 changes: 3 additions & 7 deletions src/Hyperbee.Json/Evaluators/JsonPathExpressionEvaluator.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
using System.Collections.Concurrent;
using System.Text.Json;
using System.Text.Json.Nodes;
using Hyperbee.Json.Evaluators.Parser;
using Microsoft.CSharp.RuntimeBinder;

namespace Hyperbee.Json.Evaluators;

public abstract class JsonPathExpressionEvaluator<TType> : IJsonPathFilterEvaluator<TType>
public sealed class JsonPathExpressionEvaluator<TType> : IJsonPathFilterEvaluator<TType>
{
// ReSharper disable once StaticMemberInGenericType
private static readonly ConcurrentDictionary<string, Func<TType, TType, bool>> Compiled = new();

public object Evaluator( string filter, TType current, TType root )
public object Evaluate( string filter, TType current, TType root )
{
var compiled = Compiled.GetOrAdd( filter, _ => JsonPathExpression.Compile( filter, this ) );
var compiled = Compiled.GetOrAdd( filter, _ => JsonPathExpression.Compile<TType>( filter ) );

try
{
Expand All @@ -30,5 +28,3 @@ public object Evaluator( string filter, TType current, TType root )
}
}

public class JsonPathExpressionElementEvaluator : JsonPathExpressionEvaluator<JsonElement>;
public class JsonPathExpressionNodeEvaluator : JsonPathExpressionEvaluator<JsonNode>;
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,16 @@ public override Expression GetExpression( string methodName, IList<string> argum
{
if ( arguments.Count != 1 )
{
return Expression.Block(
Expression.Throw( Expression.Constant( new Exception( $"Invalid use of {Name} function" ) ) ),
Expression.Constant( 0F )
);
return Expression.Throw( Expression.Constant( new Exception( $"Invalid use of {Name} function" ) ) );
}

var queryExp = Expression.Constant( arguments[0] );
var evaluatorExp = Expression.Constant( context.Evaluator );

return Expression.Convert( Expression.Call(
CountMethod,
Expression.Call( JsonPathHelper<TType>.SelectMethod,
context.Current,
context.Root,
queryExp,
evaluatorExp ) ), typeof( float ) );
queryExp ) ), typeof( float ) );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ public class JsonPathElementFunction<TType>( ParseExpressionContext<TType> conte
protected override Expression Evaluate( ReadOnlySpan<char> data, ReadOnlySpan<char> item, ref int start, ref int from )
{
var queryExp = Expression.Constant( item.ToString() );
var evaluatorExp = Expression.Constant( context.Evaluator );


// Create a call expression for the extension method
return Expression.Call( JsonPathHelper<TType>.GetFirstElementValueMethod, context.Current, context.Root, queryExp, evaluatorExp );
return Expression.Call( JsonPathHelper<TType>.GetFirstElementValueMethod, context.Current, context.Root, queryExp );
}
}
35 changes: 18 additions & 17 deletions src/Hyperbee.Json/Evaluators/Parser/Functions/JsonPathHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Hyperbee.Json.Evaluators.Parser.Functions;

public static class JsonPathHelper<TType>
public static class JsonPathHelper<TType> //BF: Is this the right name? JsonPathFilterHelper ?
{
// ReSharper disable once StaticMemberInGenericType
public static readonly MethodInfo GetFirstElementValueMethod;
Expand All @@ -23,9 +23,9 @@ static JsonPathHelper()
{
var thisType = typeof( JsonPathHelper<TType> );

GetFirstElementValueMethod = thisType.GetMethod( nameof( GetFirstElementValue ), [typeof( TType ), typeof( TType ), typeof( string ), typeof( IJsonPathFilterEvaluator<TType> )] );
GetFirstElementMethod = thisType.GetMethod( nameof( GetFirstElement ), [typeof( TType ), typeof( TType ), typeof( string ), typeof( IJsonPathFilterEvaluator<TType> )] );
SelectMethod = thisType.GetMethod( nameof( Select ), [typeof( TType ), typeof( TType ), typeof( string ), typeof( IJsonPathFilterEvaluator<TType> )] );
GetFirstElementValueMethod = thisType.GetMethod( nameof( GetFirstElementValue ), [typeof( TType ), typeof( TType ), typeof( string ) ] );
GetFirstElementMethod = thisType.GetMethod( nameof( GetFirstElement ), [typeof( TType ), typeof( TType ), typeof( string ) ] );
SelectMethod = thisType.GetMethod( nameof( Select ), [typeof( TType ), typeof( TType ), typeof( string ) ] );

IsTruthyMethod = thisType.GetMethod( nameof( IsTruthy ) );
}
Expand Down Expand Up @@ -65,9 +65,9 @@ private static bool IsNotEmpty( JsonNode node )
};
}

public static object GetFirstElementValue( JsonElement current, JsonElement root, string query, IJsonPathFilterEvaluator<JsonElement> evaluator )
public static object GetFirstElementValue( JsonElement current, JsonElement root, string query )
{
var first = GetFirstElement( current, root, query, evaluator );
var first = GetFirstElement( current, root, query );

return first.ValueKind switch
{
Expand All @@ -83,9 +83,9 @@ public static object GetFirstElementValue( JsonElement current, JsonElement root
};
}

public static object GetFirstElementValue( JsonNode current, JsonNode root, string query, IJsonPathFilterEvaluator<JsonNode> evaluator )
public static object GetFirstElementValue( JsonNode current, JsonNode root, string query )
{
var first = GetFirstElement( current, root, query, evaluator );
var first = GetFirstElement( current, root, query );

return first?.GetValueKind() switch
{
Expand All @@ -101,30 +101,31 @@ public static object GetFirstElementValue( JsonNode current, JsonNode root, stri
};
}

public static JsonElement GetFirstElement( JsonElement current, JsonElement root, string query, IJsonPathFilterEvaluator<JsonElement> evaluator )
//BF: SelectFirst ? Is visitor optimized for first ? Could these be moved out to just use the extensions ?

public static JsonElement GetFirstElement( JsonElement current, JsonElement root, string query )
{
return new JsonPath( evaluator )
return new JsonPath()
.Select( current, root, query )
.FirstOrDefault();
}

public static JsonNode GetFirstElement( JsonNode current, JsonNode root, string query, IJsonPathFilterEvaluator<JsonNode> evaluator )
public static JsonNode GetFirstElement( JsonNode current, JsonNode root, string query )
{
return new Nodes.JsonPathNode( evaluator )
return new Nodes.JsonPathNode()
.Select( current, root, query )
.FirstOrDefault();
}

public static IEnumerable<JsonElement> Select( JsonElement current, JsonElement root, string query, IJsonPathFilterEvaluator<JsonElement> evaluator )
public static IEnumerable<JsonElement> Select( JsonElement current, JsonElement root, string query )
{
return new JsonPath( evaluator )
return new JsonPath()
.Select( current, root, query );
}

public static IEnumerable<JsonNode> Select( JsonNode current, JsonNode root, string query, IJsonPathFilterEvaluator<JsonNode> evaluator )
public static IEnumerable<JsonNode> Select( JsonNode current, JsonNode root, string query )
{
return new Nodes.JsonPathNode( evaluator )
return new Nodes.JsonPathNode()
.Select( current, root, query );
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,17 @@ public override Expression GetExpression( string methodName, IList<string> argum
{
if ( arguments.Count != 1 )
{
return //Expression.Block(
Expression.Throw( Expression.Constant( new ArgumentException( $"{Name} function has invalid parameter count." ) ) );//,
//Expression.Constant( 0F )
//);
return Expression.Throw( Expression.Constant( new ArgumentException( $"{Name} function has invalid parameter count." ) ) );
}

var queryExp = Expression.Constant( arguments[0] );
var evaluatorExp = Expression.Constant( context.Evaluator );

return Expression.Call(
LengthMethod,
Expression.Call( JsonPathHelper<TType>.GetFirstElementMethod,
context.Current,
context.Root,
queryExp,
evaluatorExp ) );
queryExp ) );
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,18 @@ public override Expression GetExpression( string methodName, IList<string> argum
{
if ( arguments.Count != 2 )
{
return Expression.Block(
Expression.Throw( Expression.Constant( new ArgumentException( $"{Name} function has invalid parameter count." ) ) ),
Expression.Constant( 0F )
);
return Expression.Throw( Expression.Constant( new ArgumentException( $"{Name} function has invalid parameter count." ) ) );
}

var queryExp = Expression.Constant( arguments[0] );
var regex = Expression.Constant( arguments[1] );
var evaluatorExp = Expression.Constant( context.Evaluator );

return Expression.Call(
MatchMethod,
Expression.Call( JsonPathHelper<TType>.GetFirstElementMethod,
context.Current,
context.Root,
queryExp,
evaluatorExp )
queryExp )
, regex );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,18 @@ public override Expression GetExpression( string methodName, IList<string> argum
{
if ( arguments.Count != 2 )
{
return Expression.Block(
Expression.Throw( Expression.Constant( new ArgumentException( $"{Name} function has invalid parameter count." ) ) ),
Expression.Constant( 0F )
);
return Expression.Throw( Expression.Constant( new ArgumentException( $"{Name} function has invalid parameter count." ) ) );
}

var queryExp = Expression.Constant( arguments[0] );
var regex = Expression.Constant( arguments[1] );
var evaluatorExp = Expression.Constant( context.Evaluator );

return Expression.Call(
SearchMethod,
Expression.Call( JsonPathHelper<TType>.GetFirstElementMethod,
context.Current,
context.Root,
queryExp,
evaluatorExp )
queryExp )
, regex );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,15 @@ public override Expression GetExpression( string methodName, IList<string> argum
{
if ( arguments.Count != 1 )
{
return Expression.Block(
Expression.Throw( Expression.Constant( new ArgumentException( $"{Name} function has invalid parameter count." ) ) ),
Expression.Constant( 0F )
);
return Expression.Throw( Expression.Constant( new ArgumentException( $"{Name} function has invalid parameter count." ) ) );
}

var queryExp = Expression.Constant( arguments[0] );
var evaluatorExp = Expression.Constant( context.Evaluator );

return Expression.Call(
JsonPathHelper<TType>.GetFirstElementValueMethod,
context.Current,
context.Root,
queryExp,
evaluatorExp );
queryExp );
}
}
4 changes: 2 additions & 2 deletions src/Hyperbee.Json/Evaluators/Parser/JsonPathExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ public class JsonPathExpression

private static readonly MethodInfo ObjectEquals = typeof( object ).GetMethod( "Equals", [typeof( object ), typeof( object )] );

public static Func<TType, TType, bool> Compile<TType>( ReadOnlySpan<char> filter, IJsonPathFilterEvaluator<TType> evaluator = null )
public static Func<TType, TType, bool> Compile<TType>( ReadOnlySpan<char> filter )
{
var currentParam = Expression.Parameter( typeof( TType ) );
var rootParam = Expression.Parameter( typeof( TType ) );
var expressionContext = new ParseExpressionContext<TType>( currentParam, rootParam, evaluator );
var expressionContext = new ParseExpressionContext<TType>( currentParam, rootParam );
var expression = Parse( filter, expressionContext );

return Expression
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

namespace Hyperbee.Json.Evaluators.Parser;

public record ParseExpressionContext<TType>( Expression Current, Expression Root, IJsonPathFilterEvaluator<TType> Evaluator );
public record ParseExpressionContext<TType>( Expression Current, Expression Root );
12 changes: 0 additions & 12 deletions src/Hyperbee.Json/Extensions/JsonElementExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,6 @@ namespace Hyperbee.Json.Extensions;

public static class JsonElementExtensions
{
// Is operations

public static bool IsNullOrUndefined( this JsonElement value )
{
return value.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined;
}

public static bool IsObjectOrArray( this JsonElement value )
{
return value.ValueKind is JsonValueKind.Array or JsonValueKind.Object;
}

// To operations

public static dynamic ToDynamic( this JsonElement value, string path = null ) => new DynamicJsonElement( ref value, path );
Expand Down
6 changes: 3 additions & 3 deletions src/Hyperbee.Json/Extensions/JsonPathSelectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ public static class JsonPathSelectExtensions
{
public static IEnumerable<JsonElement> Select( this JsonElement element, string query )
{
return new JsonPath( null ).Select( element, query );
return new JsonPath().Select( element, query );
}

public static IEnumerable<JsonElement> Select( this JsonDocument document, string query )
{
return new JsonPath( null ).Select( document.RootElement, query );
return new JsonPath().Select( document.RootElement, query );
}

public static IEnumerable<JsonNode> Select( this JsonNode node, string query )
{
return new Nodes.JsonPathNode( null ).Select( node, query );
return new Nodes.JsonPathNode().Select( node, query );
}
}

6 changes: 4 additions & 2 deletions src/Hyperbee.Json/Extensions/JsonPropertyKeyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static class JsonPropertyKeyExtensions
{
public static JsonElement GetPropertyFromKey( this JsonElement jsonElement, ReadOnlySpan<char> propertyPath )
{
if ( jsonElement.IsNullOrUndefined() || propertyPath.IsEmpty )
if ( IsNullOrUndefined( jsonElement ) || propertyPath.IsEmpty )
return default;

var splitter = new JsonPropertyKeySplitter( propertyPath );
Expand All @@ -35,11 +35,13 @@ public static JsonElement GetPropertyFromKey( this JsonElement jsonElement, Read

jsonElement = jsonElement.TryGetProperty( name!, out var value ) ? value : default;

if ( jsonElement.IsNullOrUndefined() )
if ( IsNullOrUndefined( jsonElement ) )
return default;
}

return jsonElement;

static bool IsNullOrUndefined( JsonElement value ) => value.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined;
}

public static JsonNode GetPropertyFromKey( this JsonNode jsonNode, ReadOnlySpan<char> propertyPath )
Expand Down
Loading

0 comments on commit b80b917

Please sign in to comment.