| name | corvus-mutable-documents |
| description | Create and manipulate mutable JSON documents using JsonWorkspace, JsonDocumentBuilder, and the builder pattern. Covers workspace creation (rented vs unrented), the canonical parse-build-mutate-serialize pattern, deep property mutation, array operations, cloning, and RFC 6902 JSON Patch via PatchBuilder. USE FOR: writing code that creates or modifies JSON, understanding the V5 mutation model, implementing JSON Patch operations, working with JsonWorkspace. DO NOT USE FOR: read-only parsing (use corvus-parsed-documents-and-memory), V4 mutation patterns (use corvus-v4-migration).
|
Mutable Documents
JsonWorkspace
A scoped container for pooled memory used during mutable JSON operations.
using JsonWorkspace workspace = JsonWorkspace.Create();
JsonWorkspace workspace = JsonWorkspace.CreateUnrented();
Always use a using block. Dispose() returns the workspace to the thread-local cache (rented) or disposes all child documents and returns backing arrays to ArrayPool (unrented).
Canonical Mutation Pattern
using JsonWorkspace workspace = JsonWorkspace.Create();
using ParsedJsonDocument<JsonElement> sourceDoc = ParsedJsonDocument<JsonElement>.Parse(json);
using JsonDocumentBuilder<JsonElement.Mutable> builder =
sourceDoc.RootElement.CreateBuilder(workspace);
JsonElement.Mutable root = builder.RootElement;
root.SetProperty("name"u8, "new value"u8);
root.RemoveProperty("oldProp"u8);
string result = root.ToString();
Multiple Builders Per Workspace
using JsonWorkspace workspace = JsonWorkspace.Create();
using var builder1 = doc1.RootElement.CreateBuilder(workspace);
using var builder2 = doc2.RootElement.CreateBuilder(workspace);
Empty Builder
using JsonDocumentBuilder<JsonElement.Mutable> builder =
workspace.CreateBuilder<JsonElement.Mutable>(initialCapacity: 30, initialValueBufferSize: 8192);
Cloning
Clone() produces an immutable ParsedJsonDocument-backed element that outlives the builder:
JsonElement clone;
using (JsonWorkspace workspace = JsonWorkspace.Create())
using (ParsedJsonDocument<JsonElement> parsedDoc = ParsedJsonDocument<JsonElement>.Parse("[[[]]]"))
using (JsonDocumentBuilder<JsonElement.Mutable> doc = parsedDoc.RootElement.CreateBuilder(workspace))
{
clone = doc.RootElement[0].Clone();
}
Assert.AreEqual("[[]]", clone.GetRawText());
Version Tracking
JsonDocumentBuilder<T> tracks a ulong _version. When the builder is mutated, the version increments. Stale element references (from before the mutation) throw InvalidOperationException.
JSON Patch (RFC 6902)
using JsonWorkspace workspace = JsonWorkspace.Create();
using var doc = ParsedJsonDocument<JsonElement>.Parse(json);
using var builder = doc.RootElement.CreateBuilder(workspace);
JsonPatchDocument patch = builder.RootElement.BeginPatch(workspace)
.Add("/name"u8, "Alice")
.Remove("/obsolete"u8)
.Replace("/version"u8, 2)
.GetPatchAndDispose();
bool success = builder.RootElement.TryApplyPatch(patch);
All six RFC 6902 operations: Add, Remove, Replace, Move, Copy, Test.
Performance tip: Use UTF-8 byte literal paths ("/name"u8) for zero-allocation path handling.
Atomicity: TryApplyPatch returns false on the first failing operation, leaving partial state. For atomic semantics, snapshot the builder state before applying.
Common Pitfalls
- Forgetting to dispose workspace/builder: Both rent from pools. Missing
using leaks memory.
- Using stale element references: After mutating a builder, any previously-obtained element references are invalidated.
- Not using
using with BeginPatch(): The patch builder must be disposed via GetPatchAndDispose(). The returned JsonPatchDocument is backed by the workspace — keep the workspace alive for the lifetime of the patch.
Cross-References
- For read-only parsing, see
corvus-parsed-documents-and-memory
- For dispose analyzers (CTJ004-006), see
corvus-analyzers
- For V4→V5 mutation model changes, see
corvus-v4-migration