Skip to content

DecSmith42/json-extensions

Repository files navigation

DecSm.Extensions.Json

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.

Install

From your project directory:

dotnet add package DecSm.Extensions.Json

Getting started

The helpers operate on System.Text.Json.Nodes (JsonObject, JsonArray, JsonValue). Add these using statements:

using System.Text.Json.Nodes;
using DecSm.Extensions.Json;

Path conventions

  • 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.

Flatten a JSON node

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")
// ]

Unflatten to a JSON object

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"]}}

Replace a single value in-place

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.

Apply many replacements in-place

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-place

Important 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).

Projects in this repo

  • 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

Build, test, and benchmarks with Atom

This repository uses Atom for local automation and CI workflows.

Install Atom as a global .NET tool:

dotnet tool install -g decsm.atom.tool

Run 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 and test with the .NET CLI (without Atom)

# Build the solution
dotnet build -c Release

# Run tests
dotnet test DecSm.Extensions.Json.Tests -c Release

Requirements

  • .NET SDK 9.0 or later
  • Library targets: net8.0; net9.0;

License

This project is licensed under the MIT License. See LICENSE.txt for details.

About

Dec's Json Extensions

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages