JsonLinq is a simple, elegant .NET library to query over any type of JSON data using a fluent, LINQ-like API. It'll make your life easier by bringing the comfort of LINQ-style querying directly to your JSON.
Inspired by php-jsonq by Nahid Bin Azhar.
- Repository: https://github.com/hard-rox/json-linq
- NuGet package: https://www.nuget.org/packages/JsonLinq
- Supported target frameworks:
net8.0,net9.0,net10.0
dotnet add package JsonLinqOr in your .csproj:
<PackageReference Include="JsonLinq" Version="*" />You can start using this library right away by parsing your JSON data from a string:
using JsonLinq;
var q = JsonQuery.Parse(json);From a file:
var q = JsonQuery.ParseFile("data.json");Or asynchronously:
var q = await JsonQuery.ParseFileAsync("data.json");You can then query your data using methods such as From, Where, OrderBy, GroupBy, and more. Aggregate results with Sum, Average, Max, Min, and Count.
Let's see a quick example. Given this JSON:
// data.json
{
"employees": [
{ "id": 1, "name": "Alice", "age": 32, "department": "Engineering", "salary": 90000, "active": true, "address": { "city": "San Francisco", "state": "CA" }, "skills": ["C#", "Python"] },
{ "id": 2, "name": "Bob", "age": 27, "department": "Engineering", "salary": 78000, "active": false, "address": { "city": "Oakland", "state": "CA" }, "skills": ["TypeScript", "React"] },
{ "id": 3, "name": "Carol", "age": 30, "department": "Sales", "salary": 72000, "active": true, "address": { "city": "Los Angeles", "state": "CA" }, "skills": ["Salesforce"] }
]
}using JsonLinq;
var q = JsonQuery.ParseFile("data.json");
var result = q.From("employees")
.Where(e => e.Value<string>("department") == "Engineering")
.ToList();This returns all employees in the Engineering department.
Let's say you want the total salary of those employees:
decimal total = q.From("employees")
.Where(e => e.Value<string>("department") == "Engineering")
.Sum("salary");
// 168000Pretty neat, huh?
Let's explore the full API to see what else this library can do for you.
All examples below use the sample JSON above.
List of API:
- JsonLinq
- Installation
- Usage
- API
From(path)Find(path)At(path)Where(predicate)OrderBy(field)OrderByDescending(field)GroupBy(field)Chunk(size)Select(fields)Distinct()Take(count)Skip(count)Sum(field)Average(field)Max(field)Min(field)Count()FirstOrDefault()LastOrDefault()SingleOrDefault()Nth(index)Exists()ToList()Copy()Value<T>(key)/ValueAt<T>(path)(extension methods)
- Bugs and Issues
- Inspired By
path— dot-notated path to a property in the JSON document.
Moves the query scope to the node at the given path. If the target node is a JSON array, each element becomes an item in the working set. Subsequent query methods operate over that set.
example:
var result = q.From("employees").ToList();
// Returns all 3 employee nodesTo navigate deeper:
var result = q.From("employees.0.address").ToList();
// Scopes to Alice's address objectpath— dot-notated path to a property.
Returns the raw JsonNode? at the given path. Unlike From, this terminates the chain and returns the node directly — you cannot chain further query methods after it.
example:
JsonNode? node = q.Find("employees.0.name");
// JsonValue "Alice"Alias for Find(path). See Find.
predicate—Func<JsonNode?, bool>— a lambda that receives each item and returnstrueto keep it.
Filters the current working set. Use the Value<T>(key) and ValueAt<T>(path) extension methods inside the predicate to read field values with automatic type conversion.
Multiple Where calls are AND-ed together.
example:
var result = q.From("employees")
.Where(e => e.Value<string>("department") == "Engineering")
.ToList();Chaining multiple conditions:
var result = q.From("employees")
.Where(e => e.Value<string>("department") == "Engineering")
.Where(e => e.Value<int>("age") > 28)
.ToList();
// Only Alice (age 32, Engineering)For nested fields use ValueAt<T>:
var result = q.From("employees")
.Where(e => e.ValueAt<string>("address.city") == "Oakland")
.ToList();
// Bobfield— dot-notated path of the field to sort by.
Sorts the working set by field in ascending order.
example:
var result = q.From("employees")
.OrderBy("salary")
.ToList();
// Carol (72000), Bob (78000), Alice (90000)field— dot-notated path of the field to sort by.
Sorts the working set by field in descending order.
example:
var result = q.From("employees")
.OrderByDescending("age")
.ToList();
// Alice (32), Carol (30), Bob (27)field— the property to group by.
Groups items by the value of field. Returns a new working set where each item is an object with "key" and "items" properties.
example:
var result = q.From("employees")
.GroupBy("department")
.ToList();
// [
// { "key": "Engineering", "items": [ Alice, Bob ] },
// { "key": "Sales", "items": [ Carol ] }
// ]size— number of items per chunk. Must be greater than zero.
Splits the working set into consecutive sub-arrays of at most size elements.
example:
var result = q.From("employees")
.Chunk(2)
.ToList();
// [ [Alice, Bob], [Carol] ]fields— one or more dot-notated field names to project.
Projects each item to a new object containing only the specified fields. Missing fields are silently omitted.
example:
var result = q.From("employees")
.Select("name", "salary")
.ToList();
// [ { "name": "Alice", "salary": 90000 }, { "name": "Bob", "salary": 78000 }, ... ]Removes duplicate items from the working set. Comparison is based on the JSON string representation of each node.
example:
var result = q.From("employees")
.Select("department")
.Distinct()
.ToList();
// [ { "department": "Engineering" }, { "department": "Sales" } ]count— number of items to take from the start. Must be non-negative.
Returns at most count items from the beginning of the working set.
example:
var result = q.From("employees")
.OrderBy("salary")
.Take(2)
.ToList();
// Carol, Bob (two lowest salaries)count— number of items to skip from the start. Must be non-negative.
Skips the first count items and returns the rest.
example:
var result = q.From("employees")
.OrderByDescending("salary")
.Skip(1)
.ToList();
// Bob, Carol (skip the highest earner)field— dot-notated path of the numeric field.
Returns the sum of field across all items in the working set.
example:
decimal total = q.From("employees")
.Where(e => e.Value<string>("department") == "Engineering")
.Sum("salary");
// 168000field— dot-notated path of the numeric field.
Returns the average of field. Returns 0 if the working set is empty.
example:
decimal avg = q.From("employees").Average("salary");
// 80000field— dot-notated path of the numeric field.
Returns the maximum value of field. Returns 0 if the working set is empty.
example:
decimal max = q.From("employees").Max("salary");
// 90000field— dot-notated path of the numeric field.
Returns the minimum value of field. Returns 0 if the working set is empty.
example:
decimal min = q.From("employees").Min("salary");
// 72000Returns the number of items in the working set.
example:
int count = q.From("employees")
.Where(e => e.Value<bool>("active"))
.Count();
// 2Returns the first item in the working set, or null if the set is empty.
example:
JsonNode? first = q.From("employees")
.OrderBy("age")
.FirstOrDefault();
// Bob (youngest)Returns the last item in the working set, or null if the set is empty.
example:
JsonNode? last = q.From("employees")
.OrderBy("age")
.LastOrDefault();
// Alice (oldest)Returns the only item in the working set, or null if empty. Throws InvalidOperationException if the set contains more than one element.
example:
JsonNode? employee = q.From("employees")
.Where(e => e.Value<int>("id") == 1)
.SingleOrDefault();
// Aliceindex— zero-based index of the element to return.
Returns the item at position index, or null if the index is out of range.
example:
JsonNode? second = q.From("employees").Nth(1);
// Bob (zero-based index 1)Returns true if the working set contains at least one item, false otherwise.
example:
bool hasEngineers = q.From("employees")
.Where(e => e.Value<string>("department") == "Engineering")
.Exists();
// trueReturns the current working set as IReadOnlyList<JsonNode?>. This is the terminal method for retrieving all results.
example:
IReadOnlyList<JsonNode?> employees = q.From("employees").ToList();Returns a deep clone of the current JsonQuery instance — including its root document, scope, and working set. Useful when you want to branch a query without affecting the original.
example:
var original = q.From("employees").Where(e => e.Value<bool>("active"));
var branch = original.Copy();
// Mutations on branch do not affect originalThese extension methods on JsonNode? live in the JsonLinq namespace alongside JsonQuery. They are the primary way to access typed field values inside a Where predicate (or anywhere you have a JsonNode?).
Value<T>(key) — returns the value of a direct child property cast to T, or default(T) if the key is missing or the cast fails.
ValueAt<T>(path) — same as Value<T> but accepts a dot-notated path for nested access.
using JsonLinq;
// Direct key
string? name = node.Value<string>("name"); // "Alice"
int age = node.Value<int>("age"); // 32
// Nested path
string? city = node.ValueAt<string>("address.city"); // "San Francisco"If you encounter any bugs or issues, feel free to open an issue on GitHub.
JsonLinq is inspired by php-jsonq — an elegant PHP package for querying JSON data by Nahid Bin Azhar. The same concept has been implemented across several languages: