diff --git a/Directory.Build.props b/Directory.Build.props
index d5bdfe6f..983787c5 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,8 +2,8 @@
1
- 2
- 1
+ 3
+ 0
diff --git a/Hyperbee.Json.sln b/Hyperbee.Json.sln
index c197bd2c..144fb64c 100644
--- a/Hyperbee.Json.sln
+++ b/Hyperbee.Json.sln
@@ -22,13 +22,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{1FA7
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{4DBDB7F5-3F66-4572-80B5-3322449C77A4}"
ProjectSection(SolutionItems) = preProject
- .github\workflows\create-prerelease.yml = .github\workflows\create-prerelease.yml
.github\workflows\create-release.yml = .github\workflows\create-release.yml
.github\workflows\format.yml = .github\workflows\format.yml
.github\workflows\issue-branch.yml = .github\workflows\issue-branch.yml
.github\workflows\publish.yml = .github\workflows\publish.yml
.github\workflows\test-report.yml = .github\workflows\test-report.yml
.github\workflows\test.yml = .github\workflows\test.yml
+ .github\workflows\update-version.yml = .github\workflows\update-version.yml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbee.Json.Tests", "test\Hyperbee.Json.Tests\Hyperbee.Json.Tests.csproj", "{97886205-1467-4EE6-B3DA-496CA3D086E4}"
@@ -41,6 +41,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{13CB9B41-0
docs\JSONPATH-SYNTAX.md = docs\JSONPATH-SYNTAX.md
EndProjectSection
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbee.Json.Cts", "test\Hyperbee.Json.Cts\Hyperbee.Json.Cts.csproj", "{CC1D3E7F-E6F1-432B-B4D1-9402AED24119}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -59,6 +61,10 @@ Global
{45C24D4B-4A0B-4FF1-AC66-38374D2455E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{45C24D4B-4A0B-4FF1-AC66-38374D2455E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{45C24D4B-4A0B-4FF1-AC66-38374D2455E9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CC1D3E7F-E6F1-432B-B4D1-9402AED24119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CC1D3E7F-E6F1-432B-B4D1-9402AED24119}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CC1D3E7F-E6F1-432B-B4D1-9402AED24119}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CC1D3E7F-E6F1-432B-B4D1-9402AED24119}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -69,6 +75,7 @@ Global
{97886205-1467-4EE6-B3DA-496CA3D086E4} = {F9B24CD9-E06B-4834-84CB-8C29E5F10BE0}
{45C24D4B-4A0B-4FF1-AC66-38374D2455E9} = {F9B24CD9-E06B-4834-84CB-8C29E5F10BE0}
{13CB9B41-0462-4812-8B13-0BFD17F2BC18} = {870D9301-BE3D-44EA-BF9C-FCC2E87FE4CD}
+ {CC1D3E7F-E6F1-432B-B4D1-9402AED24119} = {F9B24CD9-E06B-4834-84CB-8C29E5F10BE0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {32874F5B-B467-4F28-A8E2-82C2536FB228}
diff --git a/Hyperbee.Json.sln.DotSettings b/Hyperbee.Json.sln.DotSettings
index 83de03a7..441eaa58 100644
--- a/Hyperbee.Json.sln.DotSettings
+++ b/Hyperbee.Json.sln.DotSettings
@@ -51,6 +51,7 @@
TrueTrueTrue
+ TrueTrueTrueTrue
diff --git a/README.md b/README.md
index 789f2a24..f40a3e5e 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+
# Hyperbee.Json
`Hyperbee.Json` is a high-performance JSONPath parser for .NET, that supports both `JsonElement` and `JsonNode`.
@@ -12,12 +12,12 @@ The library is designed to be quick and extensible, allowing support for other J
- **`IEnumerable` Results:** Deferred execution queries with `IEnumerable`.
- **Conformant:** Adheres to the JSONPath Specification [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html).
-## JSONPath Consensus
+## JSONPath RFC
-Hyperbee.Json aims to follow the RFC and to support the [JSONPath consensus](https://cburgmer.github.io/json-path-comparison)
-when the RFC is unopinionated. When the RFC is unopinionated and where the consensus is ambiguous or not aligned with our
+Hyperbee.Json conforms to the RFC, and aims to support the [JSONPath consensus](https://cburgmer.github.io/json-path-comparison)
+when the RFC is unopinionated. When the RFC is unopinionated, and where the consensus is ambiguous or not aligned with our
performance and usability goals, we may deviate. Our goal is always to provide a robust and performant library while
-strengthening our alignment with the RFC.
+strengthening our alignment with the RFC and the community.
## Installation
@@ -31,34 +31,9 @@ dotnet add package Hyperbee.Json
### Basic Examples
-#### Selecting a Single Element
+#### Selecting Elements
```csharp
-using Hyperbee.JsonPath;
-using System.Text.Json;
-
-var json = """
-{
- "store": {
- "book": [
- { "category": "fiction" },
- { "category": "science" }
- ]
- }
-}
-""";
-
-var root = JsonDocument.Parse(json);
-var result = JsonPath.Select(root, "$.store.book[0].category");
-
-Console.WriteLine(result.First()); // Output: "fiction"
-```
-
-#### Selecting Multiple Elements
-
-```csharp
-using Hyperbee.JsonPath;
-using System.Text.Json;
var json = """
{
@@ -83,8 +58,6 @@ foreach (var item in result)
#### Filtering
```csharp
-using Hyperbee.JsonPath;
-using System.Text.Json;
var json = """
{
@@ -114,8 +87,6 @@ foreach (var item in result)
#### Working with (JsonElement, Path) pairs
```csharp
-using Hyperbee.JsonPath;
-using System.Text.Json;
var json = """
{
@@ -140,8 +111,6 @@ Console.WriteLine(path); // Output: "$.store.book[0].category
#### Working with JsonNode
```csharp
-using Hyperbee.JsonPath;
-using System.Text.Json.Nodes;
var json = """
{
@@ -160,9 +129,9 @@ var result = JsonPath.Select(root, "$.store.book[0].category");
Console.WriteLine(result.First()); // Output: "fiction"
```
-## JSONPath Syntax Reference
+## JSONPath Syntax Overview
-Here's a quick reference for JSONPath syntax:
+Here's a quick overview of JSONPath syntax:
| JSONPath | Description
|:---------------------------------------------|:-----------------------------------------------------------
@@ -177,9 +146,8 @@ Here's a quick reference for JSONPath syntax:
| `..` | Recursive descent
| `?` | Filter selector
-JSONPath expressions refer to a JSON structure in the same way as XPath expressions
-are used in combination with an XML document. JSONPath assumes the name `$` is assigned
-to the root level object.
+JSONPath expressions refer to a JSON structure, and JSONPath assumes the name `$` is assigned
+to the root JSON object.
JSONPath expressions can use dot-notation:
@@ -189,54 +157,54 @@ or bracket-notation:
$['store']['book'][0]['title']
-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.
+- JSONPath allows the wildcard symbol `*` for member names and array indices.
+- It borrows the descendant operator `..` from [E4X][e4x]
+- It uses the `@` symbol to refer to the current object.
+- It uses the `?()` syntax for filtering.
+- It uses the array slice syntax proposal `[start:end:step]` from ECMASCRIPT 4.
Expressions can be used as an alternative to explicit names or indices, as in:
- $.store.book[(@.length-1)].title
+ $.store.book[(length(@)-1)].title
-using the symbol `@` for the current object. Filter expressions are supported via
-the syntax `?()`, as in:
+Filter expressions are supported via the syntax `?()`, as in:
$.store.book[?(@.price < 10)].title
-### JSONPath Methods
+### JSONPath Functions
JsonPath expressions support basic methods calls.
| Method | Description | Example
|------------|--------------------------------------------------------|------------------------------------------------
| `length()` | Returns the length of an array or string. | `$.store.book[?(length(@.title) > 5)]`
-| `count()` | Returns the count of matching elements. | `$.store.book[?(count(@.authors) > 1)]`
-| `match()` | Returns true if a string matches a regular expression. | `$.store.book[?(match(@.title, '.*Century.*'))]`
-| `search()` | Searches for a string within another string. | `$.store.book[?(search(@.title, 'Sword'))]`
+| `count()` | Returns the count of matching elements. | `$.store.book[?(count(@.authors.) > 1)]`
+| `match()` | Returns true if a string matches a regular expression. | `$.store.book[?(match(@.title,'.*Century.*'))]`
+| `search()` | Searches for a string within another string. | `$.store.book[?(search(@.title,'Sword'))]`
| `value()` | Accesses the value of a key in the current object. | `$.store.book[?(value(@.price) < 10)]`
-You can extend the supported function set by registering your own functions.
+### JSONPath Custom Functions
-#### Example: `JsonNode` Path Function
+You can also extend the supported function set by registering your own functions.
+
+**Example:** Implement a `JsonNode` Path Function:
**Step 1:** Create a custom function that returns the path of a `JsonNode`.
```csharp
-public class PathNodeFunction() : FilterExtensionFunction( argumentCount: 1 )
+public class PathNodeFunction() : FilterExtensionFunction( PathMethodInfo, FilterExtensionInfo.MustCompare )
{
public const string Name = "path";
- private static readonly Expression PathExpression = Expression.Constant( (Func, string>) Path );
+ private static readonly MethodInfo PathMethodInfo = GetMethod( nameof( Path ) );
- protected override Expression GetExtensionExpression( Expression[] arguments )
+ private static INodeType Path( INodeType arg )
{
- return Expression.Invoke( PathExpression, arguments[0] );
- }
+ if ( arg is not NodesType nodes )
+ return Constants.Null;
- public static string Path( IEnumerable nodes )
- {
var node = nodes.FirstOrDefault();
- return node?.GetPath();
- }
+ return new ValueType( node?.GetPath() );
}
```
@@ -260,11 +228,11 @@ There are excellent libraries available for RFC-9535 .NET JsonPath.
### [JsonPath.Net](https://docs.json-everything.net/path/basics/) Json-Everything
- **Pros:**
- - Extensive JSON ecosystem.
- Comprehensive feature set.
- Deferred execution queries with `IEnumerable`.
- Strong community support.
-
+ - .NET Foundation Project.
+
- **Cons:**
- No support for `JsonElement`.
- Not quite as fast as other `System.Text.Json` implementations.
@@ -285,7 +253,7 @@ There are excellent libraries available for RFC-9535 .NET JsonPath.
- Comprehensive feature set.
- Documentation and examples.
- Strong community support.
- - Level 2 .NET Foundation Project.
+ - .NET Foundation Project.
- **Cons:**
- No support for `JsonElement`, or `JsonNode`.
@@ -296,9 +264,9 @@ There are excellent libraries available for RFC-9535 .NET JsonPath.
- Supports both `JsonElement`, and `JsonNode`.
- Deferred execution queries with `IEnumerable`.
- Extendable to support additional JSON document types and functions.
-- Consensus focused JSONPath implementation.
+- RFC conforming JSONPath implementation.
-- ## Benchmarks
+## Benchmarks
Here is a performance comparison of various queries on the standard book store document.
@@ -342,42 +310,41 @@ Here is a performance comparison of various queries on the standard book store d
```
```
-| Method | Filter | Mean | Error | StdDev | Allocated
-|:----------------------- |:-------------------------------- |:--------- |:--------- |:--------- |:---------
-| Hyperbee_JsonElement | $..* `First()` | 3.026 us | 0.3647 us | 0.0200 us | 4.22 KB
-| JsonEverything_JsonNode | $..* `First()` | 3.170 us | 0.3034 us | 0.0166 us | 3.53 KB
-| Hyperbee_JsonNode | $..* `First()` | 3.275 us | 1.7533 us | 0.0961 us | 3.37 KB
-| JsonCons_JsonElement | $..* `First()` | 5.699 us | 0.2191 us | 0.0120 us | 8.48 KB
-| Newtonsoft_JObject | $..* `First()` | 8.671 us | 1.7810 us | 0.0976 us | 14.22 KB
-| | | | | |
-| JsonCons_JsonElement | $..* | 5.772 us | 3.8960 us | 0.2136 us | 8.45 KB
-| Hyperbee_JsonElement | $..* | 8.179 us | 4.9380 us | 0.2707 us | 11.02 KB
-| Newtonsoft_JObject | $..* | 9.867 us | 0.9006 us | 0.0494 us | 14.86 KB
-| Hyperbee_JsonNode | $..* | 10.188 us | 2.0528 us | 0.1125 us | 10.83 KB
-| JsonEverything_JsonNode | $..* | 21.124 us | 5.1117 us | 0.2802 us | 36.81 KB
-| | | | | |
-| Hyperbee_JsonElement | $..price | 4.867 us | 0.1883 us | 0.0103 us | 6.37 KB
-| JsonCons_JsonElement | $..price | 4.924 us | 1.5997 us | 0.0877 us | 5.65 KB
-| Hyperbee_JsonNode | $..price | 7.827 us | 5.0475 us | 0.2767 us | 8.77 KB
-| Newtonsoft_JObject | $..price | 9.442 us | 1.0020 us | 0.0549 us | 14.4 KB
-| JsonEverything_JsonNode | $..price | 15.865 us | 2.1515 us | 0.1179 us | 27.63 KB
-| | | | | |
-| Hyperbee_JsonElement | $.store.book[?(@.price == 8.99)] | 4.550 us | 1.0340 us | 0.0567 us | 9.08 KB
-| JsonCons_JsonElement | $.store.book[?(@.price == 8.99)] | 5.341 us | 1.0738 us | 0.0589 us | 5.05 KB
-| Hyperbee_JsonNode | $.store.book[?(@.price == 8.99)] | 7.341 us | 3.6147 us | 0.1981 us | 10.63 KB
-| Newtonsoft_JObject | $.store.book[?(@.price == 8.99)] | 9.621 us | 5.1553 us | 0.2826 us | 15.84 KB
-| JsonEverything_JsonNode | $.store.book[?(@.price == 8.99)] | 11.789 us | 5.2457 us | 0.2875 us | 15.85 KB
-| | | | | |
-| Hyperbee_JsonElement | $.store.book[0] | 2.896 us | 0.1069 us | 0.0059 us | 3.41 KB
-| JsonCons_JsonElement | $.store.book[0] | 2.967 us | 0.1084 us | 0.0059 us | 3.21 KB
-| Hyperbee_JsonNode | $.store.book[0] | 3.352 us | 0.1778 us | 0.0097 us | 3.58 KB
-| JsonEverything_JsonNode | $.store.book[0] | 4.779 us | 2.9031 us | 0.1591 us | 5.96 KB
-| Newtonsoft_JObject | $.store.book[0] | 8.714 us | 2.5518 us | 0.1399 us | 14.56 KB
-```
-```
+| Method | Filter | Mean | Error | StdDev | Allocated
+|------------------------ |--------------------------------- |---------- |----------- |---------- |----------
+| Hyperbee_JsonElement | $..* `First()` | 3.186 us | 0.6615 us | 0.0363 us | 4.3 KB
+| Hyperbee_JsonNode | $..* `First()` | 3.521 us | 0.1192 us | 0.0065 us | 3.45 KB
+| JsonEverything_JsonNode | $..* `First()` | 3.545 us | 0.7400 us | 0.0406 us | 3.53 KB
+| JsonCons_JsonElement | $..* `First()` | 5.793 us | 1.3811 us | 0.0757 us | 8.48 KB
+| Newtonsoft_JObject | $..* `First()` | 9.119 us | 5.3278 us | 0.2920 us | 14.22 KB
+| | | | | |
+| JsonCons_JsonElement | $..* | 6.098 us | 2.0947 us | 0.1148 us | 8.45 KB
+| Hyperbee_JsonElement | $..* | 8.812 us | 1.6812 us | 0.0922 us | 11.1 KB
+| Hyperbee_JsonNode | $..* | 10.621 us | 1.2452 us | 0.0683 us | 10.91 KB
+| Newtonsoft_JObject | $..* | 11.037 us | 5.4690 us | 0.2998 us | 14.86 KB
+| JsonEverything_JsonNode | $..* | 23.329 us | 2.2255 us | 0.1220 us | 36.81 KB
+| | | | | |
+| Hyperbee_JsonElement | $..price | 5.248 us | 3.4306 us | 0.1880 us | 6.45 KB
+| JsonCons_JsonElement | $..price | 5.402 us | 0.3285 us | 0.0180 us | 5.65 KB
+| Hyperbee_JsonNode | $..price | 8.483 us | 2.0999 us | 0.1151 us | 8.86 KB
+| Newtonsoft_JObject | $..price | 10.109 us | 9.6403 us | 0.5284 us | 14.4 KB
+| JsonEverything_JsonNode | $..price | 17.054 us | 10.5303 us | 0.5772 us | 27.63 KB
+| | | | | |
+| Hyperbee_JsonElement | $.store.book[?(@.price == 8.99)] | 4.486 us | 3.2931 us | 0.1805 us | 5.82 KB
+| JsonCons_JsonElement | $.store.book[?(@.price == 8.99)] | 5.381 us | 3.3826 us | 0.1854 us | 5.05 KB
+| Hyperbee_JsonNode | $.store.book[?(@.price == 8.99)] | 7.354 us | 4.9887 us | 0.2734 us | 8.47 KB
+| Newtonsoft_JObject | $.store.book[?(@.price == 8.99)] | 10.519 us | 3.5514 us | 0.1947 us | 15.84 KB
+| JsonEverything_JsonNode | $.store.book[?(@.price == 8.99)] | 11.912 us | 7.6346 us | 0.4185 us | 15.85 KB
+| | | | | |
+| Hyperbee_JsonElement | $.store.book[0] | 2.722 us | 0.5813 us | 0.0319 us | 2.27 KB
+| JsonCons_JsonElement | $.store.book[0] | 3.150 us | 1.7316 us | 0.0949 us | 3.21 KB
+| Hyperbee_JsonNode | $.store.book[0] | 3.339 us | 0.1733 us | 0.0095 us | 2.77 KB
+| JsonEverything_JsonNode | $.store.book[0] | 4.974 us | 3.2013 us | 0.1755 us | 5.96 KB
+| Newtonsoft_JObject | $.store.book[0] | 9.482 us | 7.0303 us | 0.3854 us | 14.56 KB
+
```
-## Additional Documentation
+## Additioal Documentation
Additional documentation can be found in the project's `/docs` folder.
@@ -385,10 +352,11 @@ Additional documentation can be found in the project's `/docs` folder.
Hyperbee.Json is built upon the great work of several open-source projects. Special thanks to:
-- Stefan Goessner for the original [JSONPath implementation](https://goessner.net/articles/JsonPath/).
- System.Text.Json team for their work on the `System.Text.Json` library.
+- Stefan Goessner for the original [JSONPath implementation](https://goessner.net/articles/JsonPath/).
- Atif Aziz's C# port of Goessner's JSONPath library [.NET JSONPath](https://github.com/atifaziz/JSONPath).
- Christoph Burgmer [JSONPath consensus effort](https://cburgmer.github.io/json-path-comparison).
+- [JSONPath Compliance Test Suite Team](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite).
## Contributing
diff --git a/docs/ADDITIONAL-CLASSES.md b/docs/ADDITIONAL-CLASSES.md
index 73d99814..bb90dcf3 100644
--- a/docs/ADDITIONAL-CLASSES.md
+++ b/docs/ADDITIONAL-CLASSES.md
@@ -5,28 +5,18 @@ property diving, element comparisons, and dynamic property access.
### Property Diving
-Property diving acts similarly to JSON Pointer; it expects a path that returns a single element.
-Unlike JSON Pointer, property diving notation expects a singular JSON Path.
+Property diving acts **similarly** to JSON Pointer; it expects an absolute path that returns a single element.
+Unlike JSON Pointer, property diving notation expects normalized JSON Path notation.
| Method | Description
|:-----------------------------------|:-----------
| `JsonElement.FromJsonPathPointer` | Dives for properties using absolute locations like `$.store.book[2].author`
-The syntax supports singular paths; dotted notation, quoted names, and simple bracketed array accessors only.
+The syntax supports absolute (normalized) paths; dotted notation, quoted names, and simple bracketed array accessors only.
The intention is to return a single element by literal path.
-Json path style '$', wildcard '*', '..', and '[a,b]' multi-result selector notations and filters are **not** supported.
+Json path style wildcard '*', '..', and '[a,b]' multi-result selector notations and filters are **not** supported.
-```
-Examples of valid path syntax:
-
- prop1.prop2
- prop1[0]
- 'prop.2'
- prop1[0].prop2
- prop1['prop.2']
- prop1.'prop.2'[0].prop3
-```
### JsonElement Path
@@ -35,7 +25,7 @@ for a given `JsonElement`.
| Method | Description
|:---------------------------|:-----------
-| `JsonPathBuilder.GetPath` | Returns the JsonPath location string for a given element
+| `JsonPathBuilder.GetPath` | Returns the JsonPath location string for a given element
### Equality Helpers
@@ -47,10 +37,25 @@ for a given `JsonElement`.
### Dynamic Object Serialization
Basic support is provided for serializing to and from dynamic objects through the use of a custom `JsonConverter`.
-The `DynamicJsonConverter` converter class is useful for simple scenareos. It is intended as a simple helper for
-basic use cases only.
+The `DynamicJsonConverter` class is useful for simple scenareos. It is intended as a simple helper for
+basic use cases only. A helper methods `JsonHelper.ConvertToDynamic` is provided to simplify the process of
+serializing and deserializing dynamic objects.
+
+#### Example: ConvertToDynamic
+
+```csharp
+var root = JsonDocument.Parse(jsonInput); // jsonInput contains the bookstore example
+var element = JsonHelper.ConvertToDynamic( source );
+
+var book = element.store.book[0];
+var author = book.author;
+var price = book.price;
+
+Assert.IsTrue( price == 8.95 );
+Assert.IsTrue( author == "Nigel Rees" );
+```
-#### DynamicJsonConverter
+#### Example: Serialize To Dynamic
```csharp
var serializerOptions = new JsonSerializerOptions
@@ -58,7 +63,7 @@ var serializerOptions = new JsonSerializerOptions
Converters = {new DynamicJsonConverter()}
};
-// jsonInput is a string containing the bookstore json from the previous examples
+// jsonInput contains the bookstore example
var jobject = JsonSerializer.Deserialize( jsonInput, serializerOptions);
Assert.IsTrue( jobject.store.bicycle.color == "red" );
diff --git a/src/Hyperbee.Json/Descriptors/Element/ElementTypeDescriptor.cs b/src/Hyperbee.Json/Descriptors/Element/ElementTypeDescriptor.cs
index d58e9bf7..09687c88 100644
--- a/src/Hyperbee.Json/Descriptors/Element/ElementTypeDescriptor.cs
+++ b/src/Hyperbee.Json/Descriptors/Element/ElementTypeDescriptor.cs
@@ -1,6 +1,7 @@
using System.Text.Json;
using Hyperbee.Json.Descriptors.Element.Functions;
using Hyperbee.Json.Filters;
+using Hyperbee.Json.Filters.Values;
namespace Hyperbee.Json.Descriptors.Element;
@@ -8,6 +9,7 @@ public class ElementTypeDescriptor : ITypeDescriptor
{
private FilterEvaluator _evaluator;
private ElementValueAccessor _accessor;
+ private NodeTypeComparer _comparer;
public FunctionRegistry Functions { get; } = new();
@@ -17,6 +19,11 @@ public class ElementTypeDescriptor : ITypeDescriptor
public IFilterEvaluator FilterEvaluator =>
_evaluator ??= new FilterEvaluator( this );
+ public INodeTypeComparer Comparer =>
+ _comparer ??= new NodeTypeComparer( Accessor );
+
+ public bool CanUsePointer => true;
+
public ElementTypeDescriptor()
{
Functions.Register( CountElementFunction.Name, () => new CountElementFunction() );
diff --git a/src/Hyperbee.Json/Descriptors/Element/ElementValueAccessor.cs b/src/Hyperbee.Json/Descriptors/Element/ElementValueAccessor.cs
index 6de13d15..3dac8a6c 100644
--- a/src/Hyperbee.Json/Descriptors/Element/ElementValueAccessor.cs
+++ b/src/Hyperbee.Json/Descriptors/Element/ElementValueAccessor.cs
@@ -43,9 +43,18 @@ internal class ElementValueAccessor : IValueAccessor
}
[MethodImpl( MethodImplOptions.AggressiveInlining )]
- public JsonElement GetElementAt( in JsonElement value, int index )
+ public bool TryGetElementAt( in JsonElement value, int index, out JsonElement element )
{
- return value[index];
+ element = default;
+
+ if ( index < 0 ) // flip negative index to positive
+ index = value.GetArrayLength() + index;
+
+ if ( index < 0 || index >= value.GetArrayLength() ) // out of bounds
+ return false;
+
+ element = value[index];
+ return true;
}
[MethodImpl( MethodImplOptions.AggressiveInlining )]
@@ -67,7 +76,7 @@ public int GetArrayLength( in JsonElement value )
: 0;
}
- public bool TryGetChildValue( in JsonElement value, string childSelector, out JsonElement childValue )
+ public bool TryGetChildValue( in JsonElement value, string childSelector, SelectorKind selectorKind, out JsonElement childValue )
{
switch ( value.ValueKind )
{
@@ -77,9 +86,17 @@ public bool TryGetChildValue( in JsonElement value, string childSelector, out Js
break;
case JsonValueKind.Array:
+ if ( selectorKind == SelectorKind.Name )
+ break;
+
if ( int.TryParse( childSelector, NumberStyles.Integer, CultureInfo.InvariantCulture, out var index ) )
{
- if ( index >= 0 && index < value.GetArrayLength() )
+ var arrayLength = value.GetArrayLength();
+
+ if ( index < 0 ) // flip negative index to positive
+ index = arrayLength + index;
+
+ if ( index >= 0 && index < arrayLength )
{
childValue = value[index];
return true;
@@ -163,4 +180,9 @@ public bool TryGetValueFromNode( JsonElement element, out object value )
return true;
}
+
+ public bool TryGetFromPointer( in JsonElement element, JsonPathSegment segment, out JsonElement childValue )
+ {
+ return element.TryGetFromJsonPathPointer( segment, out childValue );
+ }
}
diff --git a/src/Hyperbee.Json/Descriptors/Element/Functions/CountElementFunction.cs b/src/Hyperbee.Json/Descriptors/Element/Functions/CountElementFunction.cs
index c2073206..30c22efe 100644
--- a/src/Hyperbee.Json/Descriptors/Element/Functions/CountElementFunction.cs
+++ b/src/Hyperbee.Json/Descriptors/Element/Functions/CountElementFunction.cs
@@ -1,22 +1,25 @@
-using System.Linq.Expressions;
+using System.Reflection;
using System.Text.Json;
-
using Hyperbee.Json.Filters.Parser;
+using Hyperbee.Json.Filters.Values;
namespace Hyperbee.Json.Descriptors.Element.Functions;
-public class CountElementFunction() : FilterExtensionFunction( argumentCount: 1 )
+public class CountElementFunction() : FilterExtensionFunction( CountMethodInfo, FilterExtensionInfo.MustCompare )
{
public const string Name = "count";
- private static readonly Expression CountExpression = Expression.Constant( (Func, float>) Count );
-
- protected override Expression GetExtensionExpression( Expression[] arguments )
- {
- return Expression.Invoke( CountExpression, arguments[0] );
- }
+ private static readonly MethodInfo CountMethodInfo = GetMethod( nameof( Count ) );
- public static float Count( IEnumerable elements )
+ public static INodeType Count( INodeType input )
{
- return elements.Count();
+ switch ( input )
+ {
+ case NodesType nodes:
+ if ( nodes.IsNormalized && !nodes.Any() )
+ return new ValueType( 1F );
+ return new ValueType( nodes.Count() );
+ default:
+ return new ValueType( 1F );
+ }
}
}
diff --git a/src/Hyperbee.Json/Descriptors/Element/Functions/LengthElementFunction.cs b/src/Hyperbee.Json/Descriptors/Element/Functions/LengthElementFunction.cs
index 7a40e282..fc2372ce 100644
--- a/src/Hyperbee.Json/Descriptors/Element/Functions/LengthElementFunction.cs
+++ b/src/Hyperbee.Json/Descriptors/Element/Functions/LengthElementFunction.cs
@@ -1,28 +1,42 @@
-using System.Linq.Expressions;
+using System.Reflection;
using System.Text.Json;
using Hyperbee.Json.Filters.Parser;
+using Hyperbee.Json.Filters.Values;
namespace Hyperbee.Json.Descriptors.Element.Functions;
-public class LengthElementFunction() : FilterExtensionFunction( argumentCount: 1 )
+public class LengthElementFunction() : FilterExtensionFunction( LengthMethodInfo, FilterExtensionInfo.MustCompare | FilterExtensionInfo.ExpectNormalized )
{
public const string Name = "length";
- private static readonly Expression LengthExpression = Expression.Constant( (Func, float>) Length );
+ private static readonly MethodInfo LengthMethodInfo = GetMethod( nameof( Length ) );
- protected override Expression GetExtensionExpression( Expression[] arguments )
+ public static INodeType Length( INodeType input )
{
- return Expression.Invoke( LengthExpression, arguments[0] );
+ return input switch
+ {
+ NodesType nodes => LengthImpl( nodes.FirstOrDefault() ),
+ ValueType valueString => new ValueType( valueString.Value.Length ),
+ Null or Nothing => input,
+ _ => Constants.Nothing
+ };
}
- public static float Length( IEnumerable elements )
+ public static INodeType LengthImpl( object value )
{
- var element = elements.FirstOrDefault();
- return element.ValueKind switch
+ return value switch
{
- JsonValueKind.String => element.GetString()?.Length ?? 0,
- JsonValueKind.Array => element.GetArrayLength(),
- JsonValueKind.Object => element.EnumerateObject().Count(),
- _ => 0
+ string str => new ValueType( str.Length ),
+ Array array => new ValueType( array.Length ),
+ System.Collections.ICollection collection => new ValueType( collection.Count ),
+ System.Collections.IEnumerable enumerable => new ValueType( enumerable.Cast