Introduction
Invex.Extensions.Json adds path-based operations to the System.Text.Json DOM
(JsonNode, JsonObject, JsonArray): flatten a tree into path → value pairs, rebuild a tree
from those pairs, and update values in an existing object by path. Read this page to decide whether
the library fits your problem and which of its two API surfaces to use.
When to use it
- Flat key/value stores. Configuration providers, environment variables, and
.properties-style files that only understandkey = valuestrings. - ETL and data exchange. Moving JSON documents through systems that store columns or key/value pairs rather than trees.
- Diffing and patching. Comparing two documents path by path, or applying a set of
path → valueoverrides to a template document. - Inspecting structure. Checking whether an object is already flat before deciding how to process it.
When not to use it
- Object serialization. To map JSON to and from .NET types, use
JsonSerializer; this library works on the node DOM only. - Query languages. It does not implement JSONPath, JSON Pointer, or JSON Patch. Paths are plain separator-joined segments with no wildcards, filters, or escaping.
- Property names that contain the separator. Names are not escaped, so a separator inside a name becomes a path boundary when unflattening. Choose a different separator, or a different tool.
- Empty containers that must survive. Empty objects and arrays produce no flattened entries, so they are lost on every round trip.
Key concepts at a glance
| Concept | Meaning |
|---|---|
| Path | Segments joined by a separator, e.g. user:address:city. |
| Separator | The string between segments. Defaults to ":"; may be several characters, e.g. "__". |
| Leaf | A JSON primitive or JSON null. Only leaves produce flattened entries. |
| Bracketed index | users:[0]:name, used by JsonExtensions.Flatten and Unflatten. |
| Bare numeric index | users:0:name, used by the JsonObject extension members and ReplaceValues. |
Two API surfaces
The library has two complementary path and value models. Pair operations from the same column when round-tripping.
JsonExtensions |
JsonObject extension members (JsonUtil) |
|
|---|---|---|
| Flattening input | Any JsonNode |
JsonObject |
| Array path syntax | Bracketed: users:[0]:name |
Bare numeric: users:0:name |
| Flattened values | string? |
Original JSON nodes, or string? for the dictionary method |
| Unflattened arrays | Append order; no sparse padding | Numeric indices; sparse indices padded with null |
| Best suited to | Configuration and string key/value stores | Lossless JSON round-tripping |
Pick JsonExtensions when the destination only holds strings anyway. Pick the JsonObject
extension members (ToFlattenedJsonObject and ToUnflattenedJsonObject) when numbers must stay
numbers and booleans must stay booleans. In both cases, use ReplaceValue or ReplaceValues to
change values in a document you already have.
Warning
The two array conventions are not interchangeable. Do not pass bracketed paths from Flatten
to ReplaceValues, and do not mix JsonExtensions.Unflatten with ToFlattenedJsonObject.
Design principles
- Readable paths over a query language. One path format, one separator, no escaping rules to learn.
- Sources are not modified. Flattening and unflattening always return new objects; the
JsonObjectmembers deep-clone leaf nodes so results share no mutable nodes with their input. - Replacement does not build structure.
ReplaceValueandReplaceValuesmutate in place but never create intermediate objects or arrays. A path that cannot be fully traversed still falls back to writing a property on the deepest object reached, so a mistyped path can add a property; see the fallback rules in Replacing values. - Allocation-conscious.
JsonExtensions.Flattenbuilds every path in one reusableStringBuilder, and no operation uses reflection or regular expressions.