Skip to content

Commit

Permalink
[FEATURE]: Add JsonPatch and JsonDiff (#59)
Browse files Browse the repository at this point in the history
* Added JsonPatch and JsonDiff
* Added JsonPointer and JsonPathPointerConverter
* Improved ValueStringBuilder performance improvement
* Improve IRegex rewrite code
* Improved Test coverage
* Added support for initial documentation in GitHub Pages
* Updated Namespaces for Path, Pointer, Patch

---------

Co-authored-by: Matt Edwards <[email protected]>
Co-authored-by: Brenton Farmer <[email protected]>
  • Loading branch information
3 people authored Aug 1, 2024
1 parent 97b5ba1 commit 8e5529d
Show file tree
Hide file tree
Showing 134 changed files with 5,658 additions and 1,311 deletions.
16 changes: 6 additions & 10 deletions Hyperbee.Json.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31912.275
Expand Down Expand Up @@ -36,16 +35,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbee.Json.Tests", "test
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbee.Json.Benchmark", "test\Hyperbee.Json.Benchmark\Hyperbee.Json.Benchmark.csproj", "{45C24D4B-4A0B-4FF1-AC66-38374D2455E9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{13CB9B41-0462-4812-8B13-0BFD17F2BC18}"
ProjectSection(SolutionItems) = preProject
docs\ADDITIONAL-CLASSES.md = docs\ADDITIONAL-CLASSES.md
docs\index.md = docs\index.md
docs\JSONPATH-SYNTAX.md = docs\JSONPATH-SYNTAX.md
docs\_config.yml = docs\_config.yml
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
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "docs", "docs\docs.shproj", "{FC3B0A95-4DA0-43A0-A19D-624152BDF2F6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -77,10 +70,13 @@ Global
{4DBDB7F5-3F66-4572-80B5-3322449C77A4} = {1FA7CE2A-C9DA-4DC3-A242-5A7EAF8EE4FC}
{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}
{FC3B0A95-4DA0-43A0-A19D-624152BDF2F6} = {870D9301-BE3D-44EA-BF9C-FCC2E87FE4CD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {32874F5B-B467-4F28-A8E2-82C2536FB228}
EndGlobalSection
GlobalSection(SharedMSBuildProjectFiles) = preSolution
docs\docs.projitems*{fc3b0a95-4da0-43a0-a19d-624152bdf2f6}*SharedItemsImports = 13
EndGlobalSection
EndGlobal
290 changes: 34 additions & 256 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,280 +1,62 @@
# Welcome to Hyperbee.Json

# Hyperbee.Json
Hyperbee.Json is a high-performance JSON library for .NET, providing robust support for JSONPath, JsonPointer, JsonPatch, and JsonDiff.
This library is optimized for speed and low memory allocations, and it adheres to relevant RFCs to ensure reliable and predictable behavior.

`Hyperbee.Json` is a high-performance JSONPath parser for .NET, that supports both `JsonElement` and `JsonNode`.
The library is designed to be quick and extensible, allowing support for other JSON document types and functions.
Unlike other libraries that support only `JsonElement` or `JsonNode`, Hyperbee.Json supports **both** types, and can be easily extended to
support additional document types and functions.

## Features

- **High Performance:** Optimized for performance and efficiency.
- **Supports:** `JsonElement` and `JsonNode`.
- **Extensible:** Easily extended to support additional JSON document types and functions.
- **`IEnumerable` Results:** Deferred execution queries with `IEnumerable`.
- **Conformant:** Adheres to the JSONPath Specification [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html).
- **Low Memory Allocations:** Designed to minimize memory usage.
- **Comprehensive JSON Support:** Supports JSONPath, JsonPointer, JsonPatch, and JsonDiff.
- **Conformance:** Adheres to the JSONPath Specification [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html), JSONPointer [RFC 6901](https://www.rfc-editor.org/rfc/rfc6901.html), and JsonPatch [RFC 6902](https://www.rfc-editor.org/rfc/rfc6902.html).
- **Supports both `JsonElement` and `JsonNode`:** Works seamlessly with both JSON document types.

## JSONPath RFC
## JSONPath

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 and the community.
JSONPath is a query language for JSON, allowing you to navigate and extract data from JSON documents using a set of path expressions.
Hyperbee.Json's JSONPath implementation is designed for optimal performance, ensuring low memory allocations and fast query execution.
It fully conforms to [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html).

## Installation
[Read more about JsonPath](jsonpath/index.md)
[Read more about JsonPath syntax](jsonpath/jsonpath-syntax.md)

Install via NuGet:

```bash
dotnet add package Hyperbee.Json
```

## Usage
## JSONPointer

### Basic Examples

#### Selecting Elements

```csharp

var json = """
{
"store": {
"book": [
{ "category": "fiction" },
{ "category": "science" }
]
}
}
""";
JSONPointer is a syntax for identifying a specific value within a JSON document. It is simple and easy to use, making it an excellent
choice for pinpointing exact values. Hyperbee.Json's JsonPointer implementation adheres to [RFC 6901](https://www.rfc-editor.org/rfc/rfc6901.html).

var root = JsonDocument.Parse(json);
var result = JsonPath.Select(root, "$.store.book[*].category");

foreach (var item in result)
{
Console.WriteLine(item); // Output: "fiction" and "science"
}
```

#### Filtering

```csharp

var json = """
{
"store": {
"book": [
{
"category": "fiction",
"price": 10
},
{
"category": "science",
"price": 15
}
]
}
}
""";
[Documentation for JsonPointer coming soon]

var root = JsonDocument.Parse(json);
var result = JsonPath.Select(root, "$.store.book[?(@.price > 10)]");

foreach (var item in result)
{
Console.WriteLine(item); // Output: { "category": "science", "price": 15 }
}
```

#### Working with (JsonElement, Path) pairs
```csharp

var json = """
{
"store": {
"book": [
{ "category": "fiction" },
{ "category": "science" }
]
}
}
""";

var root = JsonDocument.Parse(json);
var result = JsonPath.SelectPath(root, "$.store.book[0].category");

var (node, path) = result.First();

Console.WriteLine(node); // Output: "fiction"
Console.WriteLine(path); // Output: "$.store.book[0].category
```

#### Working with JsonNode

```csharp

var json = """
{
"store": {
"book": [
{ "category": "fiction" },
{ "category": "science" }
]
}
}
""";

var root = JsonNode.Parse(json);
var result = JsonPath.Select(root, "$.store.book[0].category");

Console.WriteLine(result.First()); // Output: "fiction"
```
## JSONPatch

## JSONPath Syntax Overview
JSONPatch is a format for describing changes to a JSON document. It allows you to apply partial modifications to JSON data efficiently.
Hyperbee.Json supports JsonPatch as defined in [RFC 6902](https://www.rfc-editor.org/rfc/rfc6902.html), ensuring compatibility and reliability.

Here's a quick overview of JSONPath syntax:
[Read more about JsonPatch](jsonpatch.md)

| JSONPath | Description
|:---------------------------------------------|:-----------------------------------------------------------
| `$` | Root node
| `@` | Current node
| `.<name>`, `.'<name>'`, or `."<name>"` | Object member dot operator
| `[<name>]`, or `['<name>']`, or `["<name>"]` | Object member subscript operator
| `[<index]` | Array access operator
| `[,]` | Union operator
| `[start:end:step]` | Array slice operator
| `*`, or `[*]` | Wildcard
| `..` | Recursive descent
| `?<expr>` | Filter selector
## JSONDiff

JSONPath expressions refer to a JSON structure, and JSONPath assumes the name `$` is assigned
to the root JSON object.
JSONDiff allows you to compute the difference between two JSON documents, which is useful for versioning and synchronization.
Hyperbee.Json's implementation is optimized for performance and low memory usage, adhering to the standards set in [RFC 6902](https://www.rfc-editor.org/rfc/rfc6902.html).

JSONPath expressions can use dot-notation:
[Read more about JsonDiff](jsonpatch.md)

$.store.book[0].title
## Getting Started

or bracket-notation:
To get started with Hyperbee.Json, refer to the documentation for detailed instructions and examples. Install the library via NuGet:

$['store']['book'][0]['title']

- 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 `?` 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

Filter expressions are supported via the syntax `?(<boolean expr>)`, as in:

$.store.book[?(@.price < 10)].title

### JSONPath Functions

JsonPath expressions support basic method 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'))]`
| `value()` | Accesses the value of a key in the current object. | `$.store.book[?(value(@.price) < 10)]`

### JSONPath Extended Syntax

The library extends the JSONPath expression syntax to support additional features.

| Operators | Description | Example
|---------------------|-----------------------------------------------|------------------------------------------------
| `+` `-` `*` `\` `%` | Basic math operators. | `$[?(@.a + @.b == 3)]`
| `in` | Tests is a value is in a set. | `$[[email protected] in ['a', 'b', 'c'] ]`


### JSONPath Custom Functions

You can extend the supported function set by registering your own functions.

**Example:** Implement a `JsonNode` Path Function:

**Example:** Implement a `JsonNode` Path Function:

**Step 1:** Create a custom function that returns the path of a `JsonNode`.

```csharp
public class PathNodeFunction() : ExtensionFunction( PathMethod, CompareConstraint.MustCompare )
{
public const string Name = "path";
private static readonly MethodInfo PathMethod = GetMethod<PathNodeFunction>( nameof( Path ) );

private static ScalarValue<string> Path( IValueType argument )
{
return argument.TryGetNode<JsonNode>( out var node ) ? node?.GetPath() : null;
}
}
```

**Step 2:** Register your custom function.

```csharp
JsonTypeDescriptorRegistry.GetDescriptor<JsonNode>().Functions
.Register( PathNodeFunction.Name, () => new PathNodeFunction() );
```

**Step 3:** Use your custom function in a JSONPath query.
Install via NuGet:

```csharp
var results = source.Select( "$..[?path(@) == '$.store.book[2].title']" );
```bash
dotnet add package Hyperbee.Json
```

## Why Choose [Hyperbee.Json](https://github.com/Stillpoint-Software/Hyperbee.Json) ?
## Documentation

- High Performance.
- Supports both `JsonElement`, and `JsonNode`.
- Deferred execution queries with `IEnumerable`.
- Enhanced JsonPath syntax.
- Extendable to support additional JSON document types.
- RFC conforming JSONPath implementation.

## Comparison with Other Libraries

There are excellent libraries available for RFC-9535 .NET JsonPath.

### [JsonPath.Net](https://docs.json-everything.net/path/basics/) Json-Everything

- **Pros:**
- Comprehensive feature set.
- Deferred execution queries with `IEnumerable`.
- Enhanced JsonPath syntax.
- Strong community support.

- **Cons:**
- No support for `JsonElement`.
- More memory intensive.
- Not quite as fast as other `System.Text.Json` implementations.

### [JsonCons.NET](https://danielaparker.github.io/JsonCons.Net/articles/JsonPath/JsonConsJsonPath.html)

- **Pros:**
- High performance.
- Enhanced JsonPath syntax.

- **Cons:**
- No support for `JsonNode`.
- Does not return an `IEnumerable` result (no defered query execution).

### [Json.NET](https://www.newtonsoft.com/json) Newtonsoft

- **Pros:**
- Comprehensive feature set.
- Deferred execution queries with `IEnumerable`.
- Documentation and examples.
- Strong community support.

- **Cons:**
- No support for `JsonElement`, or `JsonNode`.
Documentation can be found in the project's `/docs` folder.

## Benchmarks

Expand Down Expand Up @@ -356,10 +138,6 @@ Here is a performance comparison of various queries on the standard book store d
| JsonEverything_JsonNode | 4.612 μs | 2.0037 μs | 0.1098 μs | 5.96 KB
| Newtonsoft_JObject | 9.627 μs | 1.1498 μs | 0.0630 μs | 14.56 KB

## Additional Documentation

Additional documentation can be found in the project's `/docs` folder.

## Credits

Hyperbee.Json is built upon the great work of several open-source projects. Special thanks to:
Expand Down
2 changes: 1 addition & 1 deletion docs/ADDITIONAL-CLASSES.md → docs/additional-classes.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
layout: default
title: Additional Classes
nav_order: 3
nav_order: 99
---

## Additional Classes
Expand Down
20 changes: 20 additions & 0 deletions docs/docs.projitems
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>fc3b0a95-4da0-43a0-a19d-624152bdf2f6</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>docs</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)additional-classes.md" />
<None Include="$(MSBuildThisFileDirectory)index.md" />
<None Include="$(MSBuildThisFileDirectory)jsonpatch.md" />
<None Include="$(MSBuildThisFileDirectory)jsonpath\functions.md" />
<None Include="$(MSBuildThisFileDirectory)jsonpath\index.md" />
<None Include="$(MSBuildThisFileDirectory)jsonpath\syntax.md" />
<None Include="$(MSBuildThisFileDirectory)jsonpointer.md" />
</ItemGroup>
</Project>
Loading

0 comments on commit 8e5529d

Please sign in to comment.