Lightweight, allocation-conscious helpers for working with System.Text.Json.Nodes. Provides a simple, human-readable path notation to:
- Flatten JSON into path/value pairs
- Unflatten path/value pairs back to JSON
- Replace a single value by path
- Apply many in-place replacements by path
The library targets .NET 8.0 and 9.0.
From your project directory:
dotnet add package DecSm.Extensions.JsonThe helpers operate on System.Text.Json.Nodes (JsonObject, JsonArray, JsonValue). Add these using statements:
using System.Text.Json.Nodes;
using DecSm.Extensions.Json;- Object properties are separated by colons, e.g.
user:address:city. - Arrays use bracketed indices when flattening/unflattening, e.g.
users:[0]:name. - For in-place replacement (ReplaceValues), arrays use bare numeric segments only, e.g.
users:0:name.
var json = JsonNode.Parse("""{ "user": { "name": "John", "tags": ["admin", "user"] } }""")!;
var flattened = JsonExtensions.Flatten(json);
// flattened is an IDictionary<string, string?> like:
// [
// ("user:name", "John"),
// ("user:tags:[0]", "admin"),
// ("user:tags:[1]", "user")
// ]var flat = new Dictionary<string, string?>
{
["user:name"] = "John",
["user:tags:[0]"] = "admin",
["user:tags:[1]"] = "user",
};
var obj = JsonExtensions.Unflatten(flat);
// obj is a JsonObject:
// {"user":{"name":"John","tags":["admin","user"]}}var root = JsonNode.Parse("""{ "user": { "details": { "city": "NYC" } } }""")!.AsObject();
var updated = root.ReplaceValue("user:details:city", "LA");
// updated: {"user":{"details":{"city":"LA"}}}
// Notes:
// - Only existing objects are traversed; missing segments are not created.
// - This method does not step into arrays.
// - Setting value to null writes a JSON null.var root2 = JsonNode.Parse("""
{
"name": "A",
"user": { "address": { "city": "NYC" } },
"users": [ { "name": "Alice" }, { "name": "Bob" } ]
}
""")!.AsObject();
root2.ReplaceValues(new Dictionary<string, string?>
{
["name"] = "B", // root-level property if present
["user:address:city"] = "LA", // nested object path if present
["users:1:name"] = "Robert", // arrays use bare numeric segments
});
// root2 is modified in-placeImportant notes for ReplaceValues:
- No new properties/containers are created; only existing ones are updated.
- If a nested path can’t be fully traversed, but the root contains a literal property equal to the remaining colon-joined path, that property is updated.
- Bracketed indices like
[0]are ignored by ReplaceValues; use bare numeric segments (users:0:name).
- DecSm.Extensions.Json — the library
- DecSm.Extensions.Json.Tests — unit tests (NUnit + Shouldly)
- DecSm.Extensions.Json.Benchmarks — microbenchmarks (BenchmarkDotNet)
- _atom — Atom build definition and GitHub workflow generation
This repository uses Atom for local automation and CI workflows.
Install Atom as a global .NET tool:
dotnet tool install -g decsm.atom.toolRun tasks from the repository root:
# Pack the NuGet package
atom PackJsonExtensions
# Run unit tests
atom TestJsonExtensions
# Run benchmarks (BenchmarkDotNet); reports are published under _publish/DecSm.Extensions.Json.Benchmarks
atom BenchmarkJsonExtensions# Build the solution
dotnet build -c Release
# Run tests
dotnet test DecSm.Extensions.Json.Tests -c Release- .NET SDK 9.0 or later
- Library targets: net8.0; net9.0;
This project is licensed under the MIT License. See LICENSE.txt for details.