Hyperbee.Json 1.0.0

dotnet add package Hyperbee.Json --version 1.0.0
NuGet\Install-Package Hyperbee.Json -Version 1.0.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Hyperbee.Json" Version="1.0.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Hyperbee.Json --version 1.0.0
#r "nuget: Hyperbee.Json, 1.0.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Hyperbee.Json as a Cake Addin
#addin nuget:?package=Hyperbee.Json&version=1.0.0

// Install Hyperbee.Json as a Cake Tool
#tool nuget:?package=Hyperbee.Json&version=1.0.0

JSONPath

A C# implementation of JSONPath for .NET System.Text.Json with a plugable expression selector.

Why

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

  • Directly leverage System.Text.Json
  • Align with the draft JSONPath Specification
  • Function according to the emerging consensus of use based on the majority of existing implementations; except through concious exception or deference to the RFC.
  • Provide a plugable model for expression script handling.

JSONPath Expressions

JSONPath expressions always refers to a JSON structure in the same way as XPath expression are used in combination with an XML document. Since a JSON structure is usually anonymous and doesn't necessarily have a root member object JSONPath assumes the abstract name $ assigned to the outer level object.

JSONPath expressions can use the dot-notation:

$.store.book[0].title

or the bracket-notation:

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

for input paths. Internal or output paths will always be converted to the more general bracket-notation.

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:

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

using the symbol @ for the current object. Filter expressions are supported via the syntax ?(<boolean expr>), as in:

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

Below is a complete overview and a side-by-side comparison of the JSONPath syntax elements with its XPath counterparts:

XPath JSONPath Description
/ $ The root object/element
. @ The current object/element
/ . or [] Child operator
.. n/a Parent operator
// .. Recursive descent. JSONPath borrows this syntax from E4X.
* * Wildcard. All objects/elements regardless their names.
@ n/a Attribute access. JSON structures don't have attributes.
[] [] Subscript operator. XPath uses it to iterate over element collections and for [predicates][xpath-predicates]. In Javascript and JSON it is the native array operator.
\| [,] Union operator in XPath results in a combination of node sets. JSONPath allows alternate names or array indices as a set.
n/a [start:end:step] Array slice operator borrowed from ES4.
[] ?() Applies a filter (script) expression.
n/a () Script expression, using the underlying script engine.
() n/a Grouping in XPath

Examples

Given a simple JSON structure that represents a bookstore:

{ "store": {
    "book": [
        { "category": "reference",
        "author": "Nigel Rees",
        "title": "Sayings of the Century",
        "price": 8.95
        },
        { "category": "fiction",
        "author": "Evelyn Waugh",
        "title": "Sword of Honour",
        "price": 12.99
        },
        { "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
        },
        { "category": "fiction",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
        }
    ],
    "bicycle": {
        "color": "red",
        "price": 19.95
    }
    }
}
XPath JSONPath Result Notes
/store/book/author $.store.book[*].author The authors of all books in the store
//author $..author All authors
/store/* $.store.* All things in store, which are some books and a red bicycle
/store//price $.store..price The price of everything in the store
//book[3] $..book[2] The third book
//book[last()] $..book[(@.length-1)]<br>$..book[-1:] The last book in order
//book[position()<3] $..book[0,1]<br>$..book[:2] The first two books
//book/*[self::category|self::author] or //book/(category,author) in XPath 2.0 $..book[category,author] The categories and authors of all books
//book[isbn] $..book[?(@.isbn)] Filter all books with isbn number
//book[price<10] $..book[?(@.price<10)] Filter all books cheapier than 10
//*[price>19]/.. $..[?(@.price>19)] Categories with things more expensive than 19 Parent (caret) not present in original spec
//* $..* All elements in XML document; all members of JSON structure
/store/book/[position()!=1] $.store.book[?(@path !== "$[\'store\'][\'book\'][0]")] All books besides that at the path pointing to the first @path not present in original spec

Script Evaluators

Hyperbee.Json provides out-of-the-box expression evaluators for handling JsonPath filter selectors.

Name Description
JsonPathCSharpEvaluator A Roslyn based expression evaluator that supports [(@...)] and [?(@...)] expresison syntax
JsonPathFuncEvaluator A simple Func<> evaluator suitable for simple, custom expression handling
JsonPathNullEvaluator An evaluator that does nothing

You can create your own evaluator by deriving from IJsonPathScriptEvaluator.

public class JsonPathFuncEvaluator : IJsonPathScriptEvaluator
{
    private readonly JsonPathEvaluator _evaluator;

    public JsonPathFuncEvaluator( JsonPathEvaluator evaluator )
    {
        _evaluator = evaluator;
    }

    public object Evaluator( string script, JsonElement current, string context )
    {
        return _evaluator?.Invoke( script, current, context );
    }
}

You can set a global default for the evaluator.

JsonPath.DefaultEvaluator = new JsonPathCSharpEvaluator();

Or you can wire it up through dependency injection.

public static IServiceCollection AddJsonPath( this IServiceCollection services, IConfiguration config )
{
    services.AddTransient<IJsonPathScriptEvaluator,JsonPathCSharpEvaluator>();
    services.AddTransient<JsonPath>();

    return services;
}

Code examples

A couple of trivial code examples. Review the tests for detailed examples.

Example 1 Select the last element of an array.

const string json = @"
[
  ""first"",
  ""second"",
  ""third""
]";

var document = JsonDocument.Parse( json );
var match = document.SelectPath( "$[-1:]" ).Single();

Assert.IsTrue( match.Value.GetString() == "third" );

Example 2 Select all elemets that have a key property with a value less than 42. This example leverages bracket expressions using the Roslyn jsonpath script evaluator.

const string json = @"
[
  { ""key"": 0}, 
  { ""key"": 42}, 
  { ""key"": -1}, 
  { ""key"": 41}, 
  { ""key"": 43}, 
  { ""key"": 42.0001}, 
  { ""key"": 41.9999}, 
  { ""key"": 100}, 
  { ""some"": ""value""}
]";

var document = JsonDocument.Parse( json );
var matches = document.SelectPath( "$[?(@.key<42)]", JsonPathCSharpEvaluator.Evaluator );

// outputs 0 -1 41 41.9999

foreach( var element in matches )
{
    Console.WriteLine( document.RootElement.GetDouble() );
};

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.

DynamicJsonConverter

var serializerOptions = new JsonSerializerOptions
{
    Converters = {new DynamicJsonConverter()}
};

// jsonInput is a string containing the bookstore json from the previous examples
var jobject = JsonSerializer.Deserialize<dynamic>( jsonInput, serializerOptions);

Assert.IsTrue( jobject.store.bicycle.color == "red" );

var jsonOutput = JsonSerializer.Serialize<dynamic>( jobject, serializerOptions ) as string;

Assert.IsTrue( jsonInput == jsonOutput );
Enum handling

When deserializing, the converter will treat enumerations as strings. You can override this behavior by setting the TryReadValueHandler on the converter. This handler will allow you to intercept and convert string and numeric values during the deserialization process.

Acknowlegements

This project builds on the work of:

Stefan Gössner - Original JSONPath specification dated 2007-02-21
Atif Aziz - .NET JSONPath
Christoph Burgmer - Parser Consensus tests

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.0 91 4/8/2024