JsonPathPlus 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package JsonPathPlus --version 1.0.0
                    
NuGet\Install-Package JsonPathPlus -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="JsonPathPlus" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="JsonPathPlus" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="JsonPathPlus" />
                    
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 JsonPathPlus --version 1.0.0
                    
#r "nuget: JsonPathPlus, 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.
#:package JsonPathPlus@1.0.0
                    
#: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=JsonPathPlus&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=JsonPathPlus&version=1.0.0
                    
Install as a Cake Tool

JsonPathPlus

Lightweight JSONPath-like Stream extensions for System.Text.Json.

Extracts JSON subtrees from a Stream by path expression without loading the full document into memory. Internally uses JsonNode.ParseAsync for evaluation; a true token-streaming mode (no full parse) is on the roadmap.

Installation

dotnet add package JsonPathPlus

Quick start

using JsonPathPlus;

// First match
JsonNode? node = await stream.ExtractFirstJsonMatchAsync("$.document_data.items[0].seal");

// All matches
await foreach (var item in stream.ExtractAllJsonMatchesAsync("$.items[*].id"))
{
    Console.WriteLine(item);
}

NuGet publishing (GitHub only)

NuGet publishing is automated in GitHub Actions and does not require local package push commands.

Workflows

Required GitHub secret

Add this repository secret before publishing:

  • NUGET_API_KEY: API key from nuget.org with push permission for JsonPathPlus

Release branch format

Publishing is triggered when you push a branch with this exact pattern:

  • release_1.0.0
  • release_1.0.1

The version is taken from the branch name after release_. Only semantic versions in major.minor.patch format are accepted.

Publish flow

When a release branch is pushed, .github/workflows/publish-nuget.yml performs:

  1. Restore
  2. Build (Release) with /p:Version from branch name
  3. Test
  4. Pack with /p:PackageVersion from branch name
  5. Push package to nuget.org with --skip-duplicate

Typical commands

git checkout -b release_1.0.0
git push -u origin release_1.0.0

Notes

  • Local nupkg/snupkg outputs are ignored by git.
  • If a branch name is invalid (for example release_1.0), the publish workflow fails fast with a validation error.
  • If the same package version already exists on nuget.org, publish step is non-failing because --skip-duplicate is enabled.

Supported path syntax

Syntax Example Meaning Status
Root $ Entire document ✅ Implemented
Property $.name Named property ✅ Implemented
Dot-chain $.a.b.c Nested properties ✅ Implemented
Array index $.items[0] Single element by index ✅ Implemented
Array range $.items[1:3] Elements at indices 1 and 2 (exclusive end) ✅ Implemented
Open-start range $.items[:3] First three elements ✅ Implemented
Open-end range $.items[2:] Elements from index 2 onwards ✅ Implemented
Wildcard (array) $.items[*] All array elements ✅ Implemented
Wildcard (object) $.obj.* All property values ✅ Implemented
Recursive descent $..propertyName Property at any depth ✅ Implemented
Recursive wildcard $..* All nodes at any depth ✅ Implemented
Negative index $.items[-1] Last element ✅ Implemented
Negative range $.items[-2:] Last two elements ✅ Implemented
Union indices $.items[0,2,4] Elements at indices 0, 2 and 4 ✅ Implemented
Union properties $.obj[name,age] Multiple named properties ✅ Implemented
Existence filter $.items[?(@.isbn)] Elements that have a property ✅ Implemented
Comparison filter $.items[?(@.price < 10)] Elements matching a comparison ✅ Implemented
Logical filter $.items[?(@.p > 1 && @.p < 5)] Filters with && / \|\| ✅ Implemented
Computed index expression $.items[(@.length-1)] Index from expression evaluated against array length ✅ Implemented

API reference

// Returns the first matching node, or null if not found.
Task<JsonNode?> ExtractFirstJsonMatchAsync(this Stream stream, string? selectToken)

// Returns an async sequence of all matching nodes.
IAsyncEnumerable<JsonNode?> ExtractAllJsonMatchesAsync(this Stream stream, string? selectToken)

Passing null or "$" as selectToken returns the entire document.

Implementation roadmap

The section below details the remaining planned work. All earlier phases (negative/union indexing, filter expressions, computed index expressions) are implemented.

Phase 1 — True streaming (no full-document parse)

Goal: Eliminate the JsonNode.ParseAsync call so large streams are traversed without holding the whole document in RAM.

Feature Notes
Token-by-token path navigation Introduce a streaming navigator that reuses JsonPathParser output and walks Utf8JsonReader tokens
Streaming multi-match Yield each match as it is found; suitable for very large arrays
Memory cap option Accept a maxNodeBytes threshold; fall back to full parse above it

Implementation sketch:

  1. Add a dedicated streaming matcher (for example JsonPathStreamingMatcher) that evaluates parsed JsonPathSegment lists against Utf8JsonReader.
  2. Keep JsonPathMatcher as the in-memory strategy and select between strategies in StreamJsonExtractionExtensions.
  3. Add compatibility tests to ensure streaming and in-memory strategies return the same results for identical paths.
  4. Add benchmark comparing streaming vs full-parse for various document sizes.

JSONPath ↔ XPath quick reference

The table below maps common XPath expressions to their JSONPath equivalents as a reference. Entries marked "not present in the original spec" are extensions defined by the JSONPath-Plus JavaScript library and are not guaranteed to be implemented in this library.

XPath JSONPath Result Notes
/store/book/author $.store.book[*].author The authors of all books in the store Can also be represented without $. as store.book[*].author (not present in the original spec); $ and @ require escaping
//author $..author All authors
/store/* $.store.* All things in store (books array + bicycle object)
/store//price $.store..price The price of everything in the store
//book[3] $..book[2] The third book
//book[last()] $..book[(@.length-1)] / $..book[-1:] The last book Use [(@['...'])] for special-char properties (not present in the original spec)
//book[position()<3] $..book[0,1] / $..book[:2] The first two books
//book/(category,author) (XPath 2.0) $..book[0][category,author] Categories and authors of all books
//book[isbn] $..book[?(@.isbn)] Filter books with an ISBN Use [?@['...']] for special-char properties (not present in the original spec)
//book[price<10] $..book[?(@.price<10)] Filter books cheaper than 10
//*[name()='price' and . != 8.95] $..*[?(@property === 'price' && @ !== 8.95)] Property values of objects whose property is price and not 8.95 Add ^ after expression to get the parent object
/ $ Root of the JSON object Backtick-escape to match a literal $
//*/*|//*/*/text() $..* All elements/members beneath root
//* $.. All elements/parent components including root Not directly specified in the original spec
//*[price>19]/.. $..[?(@.price>19)]^ Parent of items with price > 19 Parent (caret) not present in the original spec
/store/*/name() (XPath 2.0) $.store.*~ Property names of store sub-object Property name (tilde) not present in the original spec
/store/book[not(. is /store/book[1])] (XPath 2.0) $.store.book[?(@path !== "$['store']['book'][0]")] All books except the first @path not present in the original spec
//book[parent::*/bicycle/color="red"]/category $..book[?(@parent.bicycle && @parent.bicycle.color === "red")].category Categories of books whose parent has a red bicycle @parent not present in the original spec
//book/*[name() != 'category'] $..book.*[?(@property !== "category")] All children of "book" except "category" @property not present in the original spec
//book[position() != 1] $..book[?(@property !== 0)] All books except the first @property not present in the original spec
/store/*/*[name(parent::*) != 'book'] $.store.*[?(@parentProperty !== "book")] Grandchildren of store whose parent isn't "book" @parentProperty not present in the original spec
//book[count(preceding-sibling::*) != 0]/*/text() $..book.*[?(@parentProperty !== 0)] Property values of all non-first book instances @parentProperty not present in the original spec
//book[price = /store/book[3]/price] $..book[?(@.price === @root.store.book[2].price)] Filter books with price equal to the third book @root not present in the original spec
//book/../*[. instance of element(*, xs:decimal)] (XPath 2.0) $..book..*@number() Numeric values within the book array @number(), @boolean(), @string(), @null(), @object(), @array(), @integer(), @scalar(), @other(), @undefined(), @function(), @nonFinite() not present in the original spec
//book/*[name()='category' and matches(., 'tion$')] (XPath 2.0) $..book.*[?(@property === "category" && @.match(/TION$/i))] Categories of books matching regex (ends in 'TION') @property not present in the original spec
//book/*[matches(name(), 'bn$')]/parent::* (XPath 2.0) $..book.*[?(@property.match(/bn$/i))]^ Books with a property matching regex (ends in 'bn') Uses parent selector ^ to return to the parent object
` (e.g., `$) Escapes the following sequence as a literal ` not present in the original spec; use `` for a literal backtick

License

MIT

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.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.

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.6.2 94 5/22/2026
1.6.1 92 5/22/2026
1.6.0 105 5/20/2026
1.5.0 99 5/20/2026
1.4.0 106 5/19/2026
1.3.1 96 5/19/2026
1.3.0 101 5/18/2026
1.2.0 99 5/18/2026
1.1.0 91 5/18/2026
1.0.0 101 5/18/2026