Skip to content

Commit

Permalink
Refactored use of JsonElement and JsonNode into different implementat…
Browse files Browse the repository at this point in the history
…ion or via type accessors.
  • Loading branch information
MattEdwardsWaggleBee committed Jun 11, 2024
1 parent 621d785 commit a846f7d
Show file tree
Hide file tree
Showing 43 changed files with 1,007 additions and 715 deletions.
12 changes: 12 additions & 0 deletions src/Hyperbee.Json/Evaluators/IJsonTypeDescriptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Hyperbee.Json.Evaluators.Parser;

namespace Hyperbee.Json.Evaluators;

public interface IJsonTypeDescriptor
{
public Dictionary<string, FunctionCreator> Functions { get; }

public IJsonValueAccessor<TElement> GetAccessor<TElement>();
public IJsonPathFilterEvaluator<TElement> GetFilterEvaluator<TElement>();
public FilterFunction GetFilterFunction( ParseExpressionContext context );
}
11 changes: 11 additions & 0 deletions src/Hyperbee.Json/Evaluators/IJsonValueAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Hyperbee.Json.Evaluators;

public interface IJsonValueAccessor<TElement>
{
IEnumerable<(TElement, string)> EnumerateChildValues( TElement value );
TElement GetElementAt( TElement value, int index );
bool IsObjectOrArray( TElement current );
bool IsArray( TElement current, out int length );
bool IsObject( TElement current );
bool TryGetChildValue( in TElement current, ReadOnlySpan<char> childKey, out TElement childValue );
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@

namespace Hyperbee.Json.Evaluators;

public sealed class JsonPathExpressionEvaluator<TType> : IJsonPathFilterEvaluator<TType>
public sealed class JsonPathFilterEvaluator<TType> : IJsonPathFilterEvaluator<TType>
{
private readonly IJsonTypeDescriptor _typeDescriptor;

// ReSharper disable once StaticMemberInGenericType
private static readonly ConcurrentDictionary<string, Func<TType, TType, bool>> Compiled = new();


public JsonPathFilterEvaluator( IJsonTypeDescriptor typeDescriptor )
{
_typeDescriptor = typeDescriptor;
}

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

try
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;

namespace Hyperbee.Json.Evaluators.Parser.Functions;
namespace Hyperbee.Json.Evaluators.Parser.Element;

public class JsonPathCountFunction<TType>( string methodName, IList<string> arguments, ParseExpressionContext<TType> context ) : ParserExpressionFunction<TType>( methodName, arguments, context )
public class CountElementFunction( string methodName, IList<string> arguments, ParseExpressionContext context ) :
FilterExpressionFunction( methodName, arguments, context )
{
public const string Name = "count";

// ReSharper disable once StaticMemberInGenericType
private static readonly MethodInfo CountMethod;

static JsonPathCountFunction()
static CountElementFunction()
{
CountMethod = typeof( Enumerable )
.GetMethods( BindingFlags.Static | BindingFlags.Public )
.First( m =>
m.Name == "Count" &&
m.GetParameters().Length == 1 &&
m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof( IEnumerable<> ) )
.MakeGenericMethod( typeof( TType ) );
.MakeGenericMethod( typeof( JsonElement ) );
}

public override Expression GetExpression( string methodName, IList<string> arguments, ParseExpressionContext<TType> context )
public override Expression GetExpression( string methodName, IList<string> arguments, ParseExpressionContext context )
{
if ( arguments.Count != 1 )
{
Expand All @@ -31,10 +32,11 @@ public override Expression GetExpression( string methodName, IList<string> argum
var queryExp = Expression.Constant( arguments[0] );

return Expression.Convert( Expression.Call(
CountMethod,
Expression.Call( JsonPathHelper<TType>.SelectMethod,
context.Current,
context.Root,
queryExp ) ), typeof( float ) );
CountMethod,
Expression.Call( FilterElementHelper.SelectElementsMethod,
context.Current,
context.Root,
queryExp ) )
, typeof( float ) );
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
using System.Linq.Expressions;

namespace Hyperbee.Json.Evaluators.Parser.Functions;
namespace Hyperbee.Json.Evaluators.Parser.Element;

public class JsonPathElementFunction<TType>( ParseExpressionContext<TType> context ) : ParserFunction<TType>
public class FilterElementFunction( ParseExpressionContext context ) : FilterFunction
{
protected override Expression Evaluate( ReadOnlySpan<char> data, ReadOnlySpan<char> item, ref int start, ref int from )
{
var queryExp = Expression.Constant( item.ToString() );

// Create a call expression for the extension method
return Expression.Call( JsonPathHelper<TType>.GetFirstElementValueMethod, context.Current, context.Root, queryExp );
return Expression.Call( FilterElementHelper.SelectFirstElementValueMethod, context.Current, context.Root, queryExp );
}
}
61 changes: 61 additions & 0 deletions src/Hyperbee.Json/Evaluators/Parser/Element/FilterElementHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.Reflection;
using System.Text.Json;

namespace Hyperbee.Json.Evaluators.Parser.Element;

public static class FilterElementHelper
{
public static readonly MethodInfo SelectFirstElementValueMethod;
public static readonly MethodInfo SelectFirstMethod;

public static readonly MethodInfo SelectElementsMethod;

static FilterElementHelper()
{
var thisType = typeof( FilterElementHelper );

SelectFirstElementValueMethod = thisType.GetMethod( nameof( SelectFirstElementValue ), [typeof( JsonElement ), typeof( JsonElement ), typeof( string )] );
SelectFirstMethod = thisType.GetMethod( nameof( SelectFirst ), [typeof( JsonElement ), typeof( JsonElement ), typeof( string )] );
SelectElementsMethod = thisType.GetMethod( nameof( SelectElements ), [typeof( JsonElement ), typeof( JsonElement ), typeof( string )] );
}

private static bool IsNotEmpty( JsonElement element )
{
return element.ValueKind switch
{
JsonValueKind.Array => element.EnumerateArray().Any(),
JsonValueKind.Object => element.EnumerateObject().Any(),
_ => false
};
}

public static object SelectFirstElementValue( JsonElement current, JsonElement root, string query )
{
var element = SelectFirst( current, root, query );

return element.ValueKind switch
{
JsonValueKind.Number => element.GetSingle(),
JsonValueKind.String => element.GetString(),
JsonValueKind.Object => IsNotEmpty( element ),
JsonValueKind.Array => IsNotEmpty( element ),
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Null => false,
JsonValueKind.Undefined => false,
_ => false
};
}

public static JsonElement SelectFirst( JsonElement current, JsonElement root, string query )
{
return SelectElements( current, root, query )
.FirstOrDefault();
}

public static IEnumerable<JsonElement> SelectElements( JsonElement current, JsonElement root, string query )
{
return JsonPath<JsonElement>
.Select( current, root, query );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Hyperbee.Json.Evaluators.Parser.Element;

public class JsonElementTypeDescriptor : IJsonTypeDescriptor
{
public Dictionary<string, FunctionCreator> Functions { get; init; }

public IJsonValueAccessor<TElement> GetAccessor<TElement>() =>
new JsonElementValueAccessor() as IJsonValueAccessor<TElement>;

public IJsonPathFilterEvaluator<TElement> GetFilterEvaluator<TElement>() =>
new JsonPathFilterEvaluator<TElement>( this );

public FilterFunction GetFilterFunction( ParseExpressionContext context ) =>
new FilterElementFunction( context );

public JsonElementTypeDescriptor()
{
Functions = new Dictionary<string, FunctionCreator>(
[
new KeyValuePair<string, FunctionCreator>( CountElementFunction.Name, ( name, arguments, context ) => new CountElementFunction( name, arguments, context ) ),
new KeyValuePair<string, FunctionCreator>( LengthElementFunction.Name, ( name, arguments, context ) => new LengthElementFunction( name, arguments, context ) ),
new KeyValuePair<string, FunctionCreator>( MatchElementFunction.Name, ( name, arguments, context ) => new MatchElementFunction( name, arguments, context ) ),
new KeyValuePair<string, FunctionCreator>( SearchElementFunction.Name, ( name, arguments, context ) => new SearchElementFunction( name, arguments, context ) ),
new KeyValuePair<string, FunctionCreator>( ValueElementFunction.Name, ( name, arguments, context ) => new ValueElementFunction( name, arguments, context ) ),
] );
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text.Json;
using Hyperbee.Json.Extensions;

namespace Hyperbee.Json;
namespace Hyperbee.Json.Evaluators.Parser.Element;

public class JsonPathElementVisitor : JsonPathVisitorBase<JsonElement>
public class JsonElementValueAccessor : IJsonValueAccessor<JsonElement>
{
internal override IEnumerable<(JsonElement, string)> EnumerateChildValues( JsonElement value )
public IEnumerable<(JsonElement, string)> EnumerateChildValues( JsonElement value )
{
switch ( value.ValueKind )
{
Expand Down Expand Up @@ -50,19 +49,19 @@ public class JsonPathElementVisitor : JsonPathVisitorBase<JsonElement>
}

[MethodImpl( MethodImplOptions.AggressiveInlining )]
internal override JsonElement GetElementAt( JsonElement value, int index )
public JsonElement GetElementAt( JsonElement value, int index )
{
return value[index];
}

[MethodImpl( MethodImplOptions.AggressiveInlining )]
internal override bool IsObjectOrArray( JsonElement value )
public bool IsObjectOrArray( JsonElement value )
{
return value.ValueKind is JsonValueKind.Array or JsonValueKind.Object;
}

[MethodImpl( MethodImplOptions.AggressiveInlining )]
internal override bool IsArray( JsonElement value, out int length )
public bool IsArray( JsonElement value, out int length )
{
if ( value.ValueKind == JsonValueKind.Array )
{
Expand All @@ -75,12 +74,12 @@ internal override bool IsArray( JsonElement value, out int length )
}

[MethodImpl( MethodImplOptions.AggressiveInlining )]
internal override bool IsObject( JsonElement value )
public bool IsObject( JsonElement value )
{
return value.ValueKind is JsonValueKind.Object;
}

internal override bool TryGetChildValue( in JsonElement value, ReadOnlySpan<char> childKey, out JsonElement childValue )
public bool TryGetChildValue( in JsonElement value, ReadOnlySpan<char> childKey, out JsonElement childValue )
{
static int? TryParseInt( ReadOnlySpan<char> numberString )
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;

namespace Hyperbee.Json.Evaluators.Parser.Element;

public class LengthElementFunction( string methodName, IList<string> arguments, ParseExpressionContext context ) : FilterExpressionFunction( methodName, arguments, context )
{
public const string Name = "length";

private static readonly MethodInfo LengthMethod;

static LengthElementFunction()
{
LengthMethod = typeof( LengthElementFunction ).GetMethod( nameof( Length ), [typeof( JsonElement )] );
}

public override Expression GetExpression( string methodName, IList<string> arguments, ParseExpressionContext context )
{
if ( arguments.Count != 1 )
{
return Expression.Throw( Expression.Constant( new ArgumentException( $"{Name} function has invalid parameter count." ) ) );
}

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

return Expression.Call(
LengthMethod,
Expression.Call( FilterElementHelper.SelectFirstMethod,
context.Current,
context.Root,
queryExp ) );
}

public static float Length( JsonElement element )
{
return element.ValueKind switch
{
JsonValueKind.String => element.GetString()?.Length ?? 0,
JsonValueKind.Array => element.GetArrayLength(),
JsonValueKind.Object => element.EnumerateObject().Count(),
_ => 0
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
using System.Text.RegularExpressions;

namespace Hyperbee.Json.Evaluators.Parser.Element;

public class MatchElementFunction( string methodName, IList<string> arguments, ParseExpressionContext context ) : FilterExpressionFunction( methodName, arguments, context )
{
public const string Name = "match";

private static readonly MethodInfo MatchMethod;

static MatchElementFunction()
{
MatchMethod = typeof( MatchElementFunction ).GetMethod( nameof( Match ), [typeof( JsonElement ), typeof( string )] );
}

public override Expression GetExpression( string methodName, IList<string> arguments, ParseExpressionContext context )
{
if ( arguments.Count != 2 )
{
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] );

return Expression.Call(
MatchMethod,
Expression.Call( FilterElementHelper.SelectFirstMethod,
context.Current,
context.Root,
queryExp )
, regex );
}

public static bool Match( JsonElement element, string regex )
{
var regexPattern = new Regex( regex.Trim( '\"', '\'' ) );
var value = $"^{element.GetString()}$";

return regexPattern.IsMatch( value );
}
}
Loading

0 comments on commit a846f7d

Please sign in to comment.