Meziantou.Framework.JsonPath 3.0.8

Prefix Reserved
dotnet add package Meziantou.Framework.JsonPath --version 3.0.8
                    
NuGet\Install-Package Meziantou.Framework.JsonPath -Version 3.0.8
                    
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="Meziantou.Framework.JsonPath" Version="3.0.8" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Meziantou.Framework.JsonPath" Version="3.0.8" />
                    
Directory.Packages.props
<PackageReference Include="Meziantou.Framework.JsonPath" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Meziantou.Framework.JsonPath --version 3.0.8
                    
#r "nuget: Meziantou.Framework.JsonPath, 3.0.8"
                    
#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.
#:package Meziantou.Framework.JsonPath@3.0.8
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Meziantou.Framework.JsonPath&version=3.0.8
                    
Install as a Cake Addin
#tool nuget:?package=Meziantou.Framework.JsonPath&version=3.0.8
                    
Install as a Cake Tool

Meziantou.Framework.JsonPath

An implementation of JSONPath (RFC 9535) for System.Text.Json and custom object models.

Usage

The types are in the Meziantou.Framework.Json namespace; JsonNode is in System.Text.Json.Nodes.

var document = JsonNode.Parse("""{"store":{"book":[{"title":"A"},{"title":"B"}]}}""");

// Parse a JSONPath expression (can be reused)
var path = JsonPath.Parse("$.store.book[*].title");

// Evaluate against a document
var result = path.Evaluate(document);
var matches = new List<string>();
foreach (var match in result)
{
    matches.Add($"{match.Path}: {match.Value}");
}

Assert.Equal(["$['store']['book'][0]['title']: A", "$['store']['book'][1]['title']: B"], matches);

Evaluation modes

Evaluate supports two modes:

  • JsonPathEvaluationMode.Lax (default): path evaluation errors produce no match.
  • JsonPathEvaluationMode.Strict: path evaluation errors throw JsonPathEvaluationException.
var doc = JsonNode.Parse("""{"a": 1}""");
var path = JsonPath.Parse("$.name");

var laxValue = path.EvaluateValue(doc, JsonPathEvaluationMode.Lax);
Assert.Null(laxValue);

Assert.Throws<JsonPathEvaluationException>(() => path.EvaluateValue(doc, JsonPathEvaluationMode.Strict));

Custom object models

Use JsonPathNavigator<TValue> to evaluate JSONPath expressions against a custom tree without converting it to JsonNode.

var myRoot = MyNode.Object(new()
{
    ["items"] = MyNode.Array(
        MyNode.Object(new() { ["name"] = MyNode.String("a"), ["enabled"] = MyNode.Boolean(true) }),
        MyNode.Object(new() { ["name"] = MyNode.String("b"), ["enabled"] = MyNode.Boolean(false) })),
});

var path = JsonPath.Parse("$.items[?@.enabled == true]");
var result = path.Evaluate(root: myRoot, navigator: MyNodeNavigator.Instance);

var matches = new List<string>();
foreach (var match in result)
{
    MyNode? node = match.Value;
    matches.Add($"{match.Path}: {node?.Properties["name"]?.Value}");
}

Assert.Equal(["$['items'][0]: a"], matches);

Here, MyNode is a small object model and MyNodeNavigator exposes it to JSONPath:

internal sealed class MyNode
{
    private MyNode(JsonPathNodeKind kind) => Kind = kind;

    public JsonPathNodeKind Kind { get; }
    public object? Value { get; private init; } // string, double or bool
    public Dictionary<string, MyNode?> Properties { get; private init; } = [];
    public List<MyNode?> Items { get; private init; } = [];

    public static MyNode Object(Dictionary<string, MyNode?> properties) => new(JsonPathNodeKind.Object) { Properties = properties };
    public static MyNode Array(params MyNode?[] items) => new(JsonPathNodeKind.Array) { Items = [.. items] };
    public static MyNode String(string value) => new(JsonPathNodeKind.String) { Value = value };
    public static MyNode Number(double value) => new(JsonPathNodeKind.Number) { Value = value };
    public static MyNode Boolean(bool value) => new(JsonPathNodeKind.Boolean) { Value = value };
}

internal sealed class MyNodeNavigator : JsonPathNavigator<MyNode>
{
    public static MyNodeNavigator Instance { get; } = new();

    // A null node is JSON null
    public override JsonPathNodeKind GetKind(MyNode? value) => value?.Kind ?? JsonPathNodeKind.Null;

    public override IEnumerable<JsonPathProperty<MyNode>> GetProperties(MyNode? value)
        => value?.Properties.Select(property => new JsonPathProperty<MyNode>(property.Key, property.Value)) ?? [];

    public override bool TryGetPropertyValue(MyNode? value, string name, out MyNode? result)
    {
        result = null;
        return value is not null && value.Properties.TryGetValue(name, out result);
    }

    public override int GetArrayLength(MyNode? value) => value?.Items.Count ?? 0;

    public override bool TryGetElement(MyNode? value, int index, out MyNode? result)
    {
        if (value is not null && index >= 0 && index < value.Items.Count)
        {
            result = value.Items[index];
            return true;
        }

        result = null;
        return false;
    }

    public override bool TryGetString(MyNode? value, out string? result)
    {
        result = value?.Value as string;
        return result is not null;
    }

    public override bool TryGetNumber(MyNode? value, out double result)
    {
        if (value?.Value is double number)
        {
            result = number;
            return true;
        }

        result = 0;
        return false;
    }

    public override bool TryGetBoolean(MyNode? value, out bool result)
    {
        if (value?.Value is bool boolean)
        {
            result = boolean;
            return true;
        }

        result = false;
        return false;
    }
}

Navigator implementations expose JSON-like semantics for the custom node type. A null node represents JSON null; a false return value from TryGetPropertyValue or TryGetElement means the member or element is missing. Arrays are zero-based, and object property order follows the navigator's GetProperties enumeration order.

Supported Features

Full RFC 9535 compliance:

  • Selectors: name (.name, ['name']), wildcard (*), index ([0], [-1]), slice ([0:3:1]), filter ([?@.price < 10])
  • Segments: child and descendant (..)
  • Filter expressions: comparisons (==, !=, <, <=, >, >=), logical operators (&&, ||, !), existence tests, parenthesized grouping
  • Built-in functions: length(), count(), match(), search(), value()
  • Normalized paths: canonical path output per RFC 9535 §2.7

Parse rejects every query that is not well-formed and valid, as RFC 9535 §2.1 requires, including queries that only look valid: !@.a == 1 (negate the comparison with !(@.a == 1)) or @[ 'a' ] == 1 (blank space inside the brackets makes a query non-singular, so it cannot be compared). The library is tested against the JSONPath Compliance Test Suite.

Regular expressions

match() and search() take an I-Regexp (RFC 9485) pattern. The implementation is a checking one (RFC 9485 §3.1): a pattern that is not an I-Regexp, such as one using \d, (?:...), a lazy quantifier or a backreference, makes the function return false instead of being handed to .NET's own regular expression engine. Patterns follow the XSD semantics RFC 9485 §4 prescribes: ^ and $ are ordinary characters, . matches any character except line feed and carriage return, and a character outside the Basic Multilingual Plane counts as one character. Two cases of the compliance test suite expect ^ and $ to be anchors, following the non-normative mapping of RFC 9485 §5.3; this library follows §4 instead.

Limits

Parsing

Filter expressions, nested filter selectors, and function arguments may nest up to 64 levels deep. Beyond that, Parse throws a FormatException and TryParse returns false. The parser is recursive, so this bound is what keeps a hostile or machine-generated expression from exhausting the stack; 64 matches the default MaxDepth of System.Text.Json and is far above any practical query.

Evaluation

Descendant segments (..) and deep equality comparisons recurse, so evaluation visits at most 256 levels of nesting; beyond that it throws JsonPathEvaluationException in both evaluation modes. The limit is higher than the parser's because documents can legitimately be deeper than expressions: values produced by System.Text.Json's own parsers cannot exceed their default MaxDepth of 64, so this only affects values built programmatically, parsed with a raised MaxDepth, or exposed by a custom navigator.

A custom JsonPathNavigator<TValue> should expose an acyclic view of its object model. A cycle — a parent back-reference, for example — is reported as this same depth error rather than recursing forever.

Number precision

Filter expressions compare numbers, both the literals in the query and the values in the document, as IEEE 754 double-precision values (JsonPathNavigator<TValue>.TryGetNumber returns a double). Numbers that a double represents exactly compare exactly. This covers every integer from -(2^53)+1 to 2^53-1, the range I-JSON (RFC 7493 §2.2) recommends for interoperability. Outside it, distinct numbers can compare as equal:

  • Integers beyond ±(2^53)-1 are rounded: $[?@ == 9007199254740993] matches 9007199254740992.
  • Decimals are rounded to the nearest double: $[?@ == 0.1] matches 0.10000000000000001.
  • Magnitudes beyond the double range become infinity: $[?@ == 1e400] matches 1e500.
  • Magnitudes below the smallest double become zero: $[?@ < 0] does not match -1e-400.

Index and slice selectors are not affected: the parser rejects any value outside the I-JSON integer range, as RFC 9535 §2.1 requires.

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

    • No dependencies.
  • net11.0

    • No dependencies.

NuGet packages (6)

Showing the top 5 NuGet packages that depend on Meziantou.Framework.JsonPath:

Package Downloads
Meziantou.Framework.InlineSnapshotTesting

Enables verification of objects using inline snapshots

Meziantou.Framework.TdsServer

A TDS server library for SQL Server protocol connections with query response serialization and ASP.NET Core hosting integration

Meziantou.Framework.Language.Json

A Roslyn-style immutable JSON syntax tree: green and red nodes, trivia, diagnostics, annotations, and editing that keeps the text you did not touch.

TruthWeaver.Yaml

YAML support for TruthWeaver: read and write rule trees and query YAML documents as a data source, isolated in its own package so only consumers of YAML take the YamlDotNet dependency.

TruthWeaver.DataSources.Json

JSON data source for TruthWeaver variable references: JsonDataSource answers JSONPath (RFC 9535) queries against a JSON document and JsonQueryValidator checks a query when a rule compiles.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.8 1,451 10/21/2026
3.0.7 3,918 9/27/2026
3.0.6 1,814 9/15/2026
3.0.5 409 9/13/2026
3.0.4 257 9/11/2026
3.0.3 899 9/6/2026
3.0.2 2,090 8/29/2026
3.0.1 8,875 7/8/2026
3.0.0 1,067 7/5/2026
2.0.4 471 7/5/2026
2.0.3 4,120 6/13/2026
2.0.2 5,285 5/23/2026
2.0.1 3,954 5/11/2026
2.0.0 1,130 5/6/2026
1.1.0 560 5/2/2026
1.0.2 3,749 4/25/2026
1.0.1 1,175 3/24/2026
1.0.0 151 3/24/2026