Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -395,4 +395,10 @@ FodyWeavers.xsd
*.msp

# JetBrains Rider
*.sln.iml
*.sln.iml

# Claude AI assistant files
.claude/
claude.md
**/claude-tasks.md
**/baseline-benchmarks.md
16 changes: 11 additions & 5 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageIcon>assets/icon.png</PackageIcon>
<PackageReleaseNotes>https://github.com/Stillpoint-Software/PostgresTest/releases/latest</PackageReleaseNotes>
<RepositoryUrl>https://github.com/Stillpoint-Software/PostgresTest</RepositoryUrl>
<PackageReleaseNotes>https://github.com/Stillpoint-Software/hyperbee.json/releases/latest</PackageReleaseNotes>
<RepositoryUrl>https://github.com/Stillpoint-Software/hyperbee.json</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/Stillpoint-Software/PostgresTest</PackageProjectUrl>
<PackageProjectUrl>https://stillpoint-software.github.io/hyperbee.json/</PackageProjectUrl>
</PropertyGroup>

<!-- Pull README & LICENSE into the package root -->
Expand All @@ -40,9 +40,15 @@
PackagePath="\"
Link="LICENSE" />
</ItemGroup>
<!-- Global project properies -->
<!-- Global project properties - .NET 10 LTS First Strategy -->
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<TargetFramework>net10.0</TargetFramework>
<!-- Primary target: .NET 10 (next LTS), with .NET 9 for current support, .NET 8 for compatibility -->
<TargetFrameworks>net10.0;net9.0;net8.0</TargetFrameworks>
</PropertyGroup>

<!-- Suppress common warnings -->
<PropertyGroup Condition="'$(IsTestProject)' == 'true'">
<NoWarn>$(NoWarn);MSTEST0001</NoWarn>
</PropertyGroup>
</Project>
25 changes: 22 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<!-- Enable Central Package Management -->
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<!-- Suppress NuGet version constraint warnings -->
<NoWarn>$(NoWarn);NU1608</NoWarn>
</PropertyGroup>

<!-- Common packages for all frameworks -->
<ItemGroup>
<!-- Core Application Dependencies -->
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="5.0.0" />
Expand All @@ -27,6 +32,20 @@
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<!-- Benchmarking -->
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
</ItemGroup>

<!-- .NET 8 specific packages (compatibility) -->
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<!-- JsonCraft.JsonPath doesn't support .NET 8 -->
</ItemGroup>

<!-- .NET 9+ specific packages -->
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0' OR '$(TargetFramework)' == 'net10.0'">
<PackageVersion Include="JsonCraft.JsonPath" Version="1.0.0" />
</ItemGroup>

<!-- .NET 10 specific packages (primary target) -->
<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
<!-- Add .NET 10 specific package versions here if needed -->
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions Hyperbee.Json.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PublicFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="I" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="__" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=2c62818f_002D621b_002D4425_002Dadc9_002D78611099bfcb/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Type parameters"&gt;&lt;ElementKinds&gt;&lt;Kind Name="TYPE_PARAMETER" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AA_BB"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="Aa_bb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=53eecf85_002Dd821_002D40e8_002Dac97_002Dfdb734542b84/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=70345118_002D4b40_002D4ece_002D937c_002Dbbeb7a0b2e70/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
Expand Down
15 changes: 15 additions & 0 deletions NuGet.Config
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>

<!-- Package source mapping for Central Package Management -->
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="Microsoft.*" />
<package pattern="System.*" />
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
4 changes: 0 additions & 4 deletions docs/.todo.md

This file was deleted.

1 change: 0 additions & 1 deletion docs/docs.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
<Import_RootNamespace>docs</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory).todo.md" />
<None Include="$(MSBuildThisFileDirectory)additional-classes.md" />
<None Include="$(MSBuildThisFileDirectory)index.md" />
<None Include="$(MSBuildThisFileDirectory)jsonpatch.md" />
Expand Down
6 changes: 3 additions & 3 deletions src/Hyperbee.Json/Dynamic/DynamicJsonElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public override bool TryGetIndex( GetIndexBinder binder, object[] indexes, out o
return true;
}

result = null;
result = null!;
return false;
}

Expand All @@ -60,7 +60,7 @@ public override bool TryGetMember( GetMemberBinder binder, out object result )
}
}

result = null;
result = null!;
return false;
}

Expand All @@ -74,7 +74,7 @@ public override bool TryInvokeMember( InvokeMemberBinder binder, object[] args,
return true;
}

result = null;
result = null!;
return false;
}

Expand Down
6 changes: 3 additions & 3 deletions src/Hyperbee.Json/Dynamic/DynamicJsonNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public override bool TryGetIndex( GetIndexBinder binder, object[] indexes, out o
return true;
}

result = null;
result = null!;
return false;
}

Expand All @@ -57,7 +57,7 @@ public override bool TryGetMember( GetMemberBinder binder, out object result )
result = new DynamicJsonNode( ref arrayValue );
return true;
default:
result = null;
result = null!;
return false;
}
}
Expand Down Expand Up @@ -86,7 +86,7 @@ public override bool TryInvokeMember( InvokeMemberBinder binder, object[] args,
return true;
}

result = null;
result = null!;
return false;
}

Expand Down
33 changes: 16 additions & 17 deletions src/Hyperbee.Json/Hyperbee.Json.csproj
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>true</IsPackable>
<Authors>Stillpoint Software, Inc.</Authors>
<PackageId>Hyperbee.Json</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>json-path;jsonpath;json-pointer;jsonpointer;json-patch;jsonpatch;query;path;patch;diff;json;rfc9535;rfc6901;rfc6902</PackageTags>
<PackageIcon>icon.png</PackageIcon>
<PackageProjectUrl>https://stillpoint-software.github.io/hyperbee.json/</PackageProjectUrl>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Copyright>Stillpoint Software, Inc.</Copyright>
<Title>Hyperbee Json</Title>
<Description>A high-performance JSON library for System.Text.Json JsonElement and JsonNode, providing robust support for JSONPath, JsonPointer, JsonPatch, and JsonDiff.</Description>
<RepositoryUrl>https://github.com/Stillpoint-Software/Hyperbee.Json</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageReleaseNotes>https://github.com/Stillpoint-Software/Hyperbee.Json/releases/latest</PackageReleaseNotes>

</PropertyGroup>
<PropertyGroup>
<IsPackable>true</IsPackable>
<Authors>Stillpoint Software, Inc.</Authors>
<PackageId>Hyperbee.Json</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>json-path;jsonpath;json-pointer;jsonpointer;json-patch;jsonpatch;query;path;patch;diff;json;rfc9535;rfc6901;rfc6902</PackageTags>
<PackageIcon>icon.png</PackageIcon>
<PackageProjectUrl>https://stillpoint-software.github.io/hyperbee.json/</PackageProjectUrl>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Copyright>Stillpoint Software, Inc.</Copyright>
<Title>Hyperbee Json</Title>
<Description>A high-performance JSON library for System.Text.Json JsonElement and JsonNode, providing robust support for JSONPath, JsonPointer, JsonPatch, and JsonDiff.</Description>
<RepositoryUrl>https://github.com/Stillpoint-Software/Hyperbee.Json</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageReleaseNotes>https://github.com/Stillpoint-Software/Hyperbee.Json/releases/latest</PackageReleaseNotes>
</PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ internal class FunctionExpressionFactory : IExpressionFactory
public static bool TryGetExpression<TNode>( ref ParserState state, out Expression expression, out CompareConstraint compareConstraint, ITypeDescriptor<TNode> descriptor )
{
compareConstraint = CompareConstraint.None;
expression = null;
expression = null!;

if ( state.Item.IsEmpty || !char.IsLetter( state.Item[0] ) )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static bool TryGetExpression<TNode>( ref ParserState state, out Expressio

if ( !TryParseNode( descriptor.NodeActions, state.Item, out var node ) )
{
expression = null;
expression = null!;
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ internal class NotExpressionFactory : IExpressionFactory
public static bool TryGetExpression<TNode>( ref ParserState state, out Expression expression, out CompareConstraint compareConstraint, ITypeDescriptor<TNode> _ = null )
{
compareConstraint = CompareConstraint.None;
expression = null;
expression = null!;

return state.Operator == Operator.Not;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public static bool TryGetExpression<TNode>( ref ParserState state, out Expressio

if ( state.Operator != Operator.OpenParen || !state.Item.IsEmpty )
{
expression = null;
expression = null!;
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static bool TryGetExpression<TNode>( ref ParserState state, out Expressio

if ( item.IsEmpty || item[0] != '$' && item[0] != '@' )
{
expression = null;
expression = null!;
return false;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Hyperbee.Json/Path/Filters/Parser/ExtensionFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ protected ExtensionFunction( MethodInfo methodInfo, CompareConstraint compareCon
internal Expression GetExpression<TNode>( ref ParserState state )
{
var arguments = new Expression[_argumentCount];
var expectNormalized = CompareConstraint.HasFlag( CompareConstraint.ExpectNormalized );
var expectNormalized = (CompareConstraint & CompareConstraint.ExpectNormalized) != 0;

for ( var i = 0; i < _argumentCount; i++ )
{
Expand Down
34 changes: 26 additions & 8 deletions src/Hyperbee.Json/Path/Filters/Parser/FilterParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ internal static Expression Parse( ref ParserState state ) // recursion entrypoin
{
MoveNext( ref state );
items.Enqueue( GetExprItem( ref state ) ); // may cause recursion

} while ( state.IsParsing );

// check for paren mismatch
Expand Down Expand Up @@ -153,11 +152,24 @@ static bool IsFinished( in ParserState state, char ch, ref int itemEnd )
private static void MoveNextOperator( ref ParserState state ) // move to the next operator
{
if ( state.Operator.IsLogical() || state.Operator.IsComparison() || state.Operator.IsMath() )
{
return;
}

if ( !state.IsParsing )
// Determine if we should stop looking for an operator.
//
// When IsParsing is false (Previous == TerminalCharacter), we've hit a potential stopping point.
// However, ')' as a terminal character is ambiguous - it could be:
// 1. A function's closing paren: `length(@.x)` - should continue to find `> 10` in `(length(@.x) > 10)`
// 2. The outer expression's closing paren - should stop
// 3. A function argument's closing paren - should stop
//
// We return early (stop) when:
// - TerminalCharacter is not ')' (e.g., ',' is unambiguous), OR
// - ParenDepth == 0 (we're at the outermost level, so ')' closes the expression), OR
// - IsArgument is true (we're parsing a function argument, so ')' is definitely ours)
//
// Otherwise, we fall through to the while loop to continue scanning for operators.

if ( !state.IsParsing && (state.TerminalCharacter != ArgClose || state.ParenDepth == 0 || state.IsArgument) )
{
state.Operator = Operator.NonOperator;
return;
Expand All @@ -174,6 +186,7 @@ private static void MoveNextOperator( ref ParserState state ) // move to the nex

private static void NextCharacter( ref ParserState state, int start, out char nextChar, ref char? quoteChar )
{
// Read next character
nextChar = state.Buffer[state.Pos++];

// Handle escape characters within quotes
Expand Down Expand Up @@ -438,25 +451,30 @@ private static void ThrowIfInvalidCompare( in ParserState state, ExprItem left,

private static void ThrowIfLiteralInvalidCompare( in ParserState state, ExprItem left, ExprItem right )
{
const CompareConstraint literalMustCompare = CompareConstraint.Literal | CompareConstraint.MustCompare;

if ( state.IsArgument || left.Operator.IsMath() )
return;

if ( left.CompareConstraint.HasFlag( CompareConstraint.Literal | CompareConstraint.MustCompare ) && !left.Operator.IsComparison() )
if ( (left.CompareConstraint & literalMustCompare) == literalMustCompare && !left.Operator.IsComparison() )
throw new NotSupportedException( $"Unsupported literal without comparison: {state.Buffer.ToString()}." );

if ( right != null && right.CompareConstraint.HasFlag( CompareConstraint.Literal | CompareConstraint.MustCompare ) && !left.Operator.IsComparison() )
if ( right != null && (right.CompareConstraint & literalMustCompare) == literalMustCompare && !left.Operator.IsComparison() )
throw new NotSupportedException( $"Unsupported literal without comparison: {state.Buffer.ToString()}." );
}

private static void ThrowIfFunctionInvalidCompare( in ParserState state, ExprItem item )
{
const CompareConstraint functionMustCompare = CompareConstraint.Function | CompareConstraint.MustCompare;
const CompareConstraint functionMustNotCompare = CompareConstraint.Function | CompareConstraint.MustNotCompare;

if ( state.IsArgument )
return;

if ( item.CompareConstraint.HasFlag( CompareConstraint.Function | CompareConstraint.MustCompare ) && !item.Operator.IsComparison() )
if ( (item.CompareConstraint & functionMustCompare) == functionMustCompare && !item.Operator.IsComparison() )
throw new NotSupportedException( $"Function must compare: {state.Buffer.ToString()}." );

if ( item.CompareConstraint.HasFlag( CompareConstraint.Function | CompareConstraint.MustNotCompare ) && item.Operator.IsComparison() )
if ( (item.CompareConstraint & functionMustNotCompare) == functionMustNotCompare && item.Operator.IsComparison() )
throw new NotSupportedException( $"Function must not compare: {state.Buffer.ToString()}." );
}

Expand Down
9 changes: 5 additions & 4 deletions src/Hyperbee.Json/Path/Filters/Parser/Operator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ public enum Operator

internal static class OperatorExtensions
{
public static bool IsNonOperator( this Operator op ) => op.HasFlag( Operator.NonOperator );
public static bool IsComparison( this Operator op ) => op.HasFlag( Operator.Comparison );
public static bool IsLogical( this Operator op ) => op.HasFlag( Operator.Logical );
public static bool IsMath( this Operator op ) => op.HasFlag( Operator.Math );
// Use direct bitwise operations instead of HasFlag() to avoid boxing allocations
public static bool IsNonOperator( this Operator op ) => (op & Operator.NonOperator) != 0;
public static bool IsComparison( this Operator op ) => (op & Operator.Comparison) != 0;
public static bool IsLogical( this Operator op ) => (op & Operator.Logical) != 0;
public static bool IsMath( this Operator op ) => (op & Operator.Math) != 0;
}
Loading