Cocoar.Json.Zero
0.1.0-alpha.5
dotnet add package Cocoar.Json.Zero --version 0.1.0-alpha.5
NuGet\Install-Package Cocoar.Json.Zero -Version 0.1.0-alpha.5
<PackageReference Include="Cocoar.Json.Zero" Version="0.1.0-alpha.5" />
<PackageVersion Include="Cocoar.Json.Zero" Version="0.1.0-alpha.5" />
<PackageReference Include="Cocoar.Json.Zero" />
paket add Cocoar.Json.Zero --version 0.1.0-alpha.5
#r "nuget: Cocoar.Json.Zero, 0.1.0-alpha.5"
#:package Cocoar.Json.Zero@0.1.0-alpha.5
#addin nuget:?package=Cocoar.Json.Zero&version=0.1.0-alpha.5&prerelease
#tool nuget:?package=Cocoar.Json.Zero&version=0.1.0-alpha.5&prerelease
Cocoar.Json.Zero
Zero Strings. Zero Leaks. Zero Leftovers.
A mutable JSON document object model (DOM) that stores all content as UTF-8 byte arrays, enabling deterministic memory cleanup for sensitive data handling.
Why Use This?
Problem: Regular JSON libraries (including System.Text.Json) store data as managed string objects. You can't control when strings are garbage collected or zeroed from memory. For sensitive data (API keys, passwords, PII), this means secrets may linger in RAM for unpredictable periods.
Solution: Cocoar.Json.Zero stores all JSON content as byte[] arrays that you can deterministically zero via Dispose(). When you're done with sensitive data, call Dispose() and it's immediately zeroed from memory—no waiting for the garbage collector.
For complete security details, see Security Implementation.
Key Features
✅ Zero-string JSON handling - All content stored as byte[] instead of managed strings
✅ Deterministic cleanup - Dispose() immediately zeros all byte arrays
✅ UTF-8 native - Works directly with UTF-8 bytes using ReadOnlySpan<byte>
✅ Mutable in-memory structure - Parse, modify, merge, transform, serialize back
✅ Built on System.Text.Json - Uses battle-tested Utf8JsonReader and Utf8JsonWriter
✅ Path navigation - Colon-separated paths ("Root:Level1:Property")
✅ Security hardened - Randomized hashing, buffer zeroing, path limits
Quick Start
using Cocoar.Json.Zero;
using System.Security.Cryptography;
// Parse from file (automatically zeros input buffer)
using var doc = JsonZeroIO.ParseFromFileAndZero("config.json");
var root = (JsonZeroObject)doc;
// Navigate using paths (UTF-8, no string allocations)
var value = root.GetByPathUtf8("Database:ConnectionString"u8);
// Transform sensitive values (original is auto-zeroed)
root.Transform("Database:Password"u8, _ => "REDACTED"u8.ToArray());
// Update values
byte[] newData = GetFromVault(); // byte[] from external source
root.SetByPathUtf8("ApiKey"u8, JsonZeroString.FromOwned(newData));
// Serialize to stream (preferred - automatic buffer zeroing)
using var fs = File.Create("output.json");
await JsonZeroDocument.WriteToAsync(root, fs);
// Dispose zeros all internal byte arrays immediately
// (happens automatically at end of 'using' block)
Use Cases
Perfect for:
- Configuration management with secrets (passwords, API keys, tokens)
- PCI-DSS, HIPAA, GDPR, SOC2 compliance scenarios requiring deterministic cleanup
- Long-running services where GC timing is unpredictable
- Data transformation pipelines with controlled cleanup
- Preventing secrets in memory dumps or page files
Not suitable for:
- General-purpose JSON (use
System.Text.Jsoninstead) - High-frequency, low-latency APIs (use specialized parsers)
- Read-only JSON processing (use
Utf8JsonReaderdirectly) - Scenarios where you need POCO mapping (use
JsonSerializer)
Core Operations
Parsing
// Parse and zero input (small-medium files)
using var doc = JsonZeroIO.ParseFromFileAndZero("config.json");
// Parse from stream (async + zeroing)
await using var stream = File.OpenRead("config.json");
using var doc = await JsonZeroIO.ParseFromStreamAndZeroAsync(stream);
// Parse large files in chunks (>100MB)
using var stream2 = File.OpenRead("large.json");
using var doc2 = JsonZeroIO.ParseFromStreamChunked(stream2);
// Parse from bytes (caller owns buffer)
using var doc3 = JsonZeroDocument.Parse(utf8Bytes);
Navigation & Manipulation
// Get value at path
var value = root.GetByPathUtf8("Parent:Child:Property"u8);
// Set value (creates intermediate objects if needed)
root.SetByPathUtf8("Config:Host"u8, new JsonZeroString("localhost"u8.ToArray()));
// Remove property
bool removed = root.RemoveByPathUtf8("Secrets:OldKey"u8);
// Clone subtree (returns deep clone, safe for concurrent use)
var subtree = root.SelectByPathUtf8("Config:Database"u8);
Transform
Transform values with automatic zeroing of original bytes. For detailed patterns (redaction, masking, tokenization, encryption), see Usage Guide.
Merge & Serialize
// Merge two configs
JsonZeroMerge.Merge(target, source);
// Serialize to stream (automatic zeroing)
await JsonZeroDocument.WriteToAsync(root, stream);
// Serialize to bytes (caller must zero)
byte[] json = JsonZeroDocument.ToUtf8Bytes(root);
try {
File.WriteAllBytes("out.json", json);
} finally {
CryptographicOperations.ZeroMemory(json);
}
Integration with System.Text.Json
Use Json.Zero for manipulation, then deserialize to POCOs with System.Text.Json:
// 1. Load and transform with Json.Zero
using var config = JsonZeroIO.ParseFromFileAndZero("config.json");
var obj = (JsonZeroObject)config;
obj.Transform("Database:Password"u8, _ => "REDACTED"u8.ToArray());
// 2. Serialize to UTF-8
byte[] json = JsonZeroDocument.ToUtf8Bytes(obj);
// 3. Deserialize to POCO (strings allocated here, but originals are zeroed)
var appConfig = JsonSerializer.Deserialize<AppConfig>(json);
// 4. Zero intermediate buffer
CryptographicOperations.ZeroMemory(json);
// 5. Use type-safe config
Console.WriteLine($"Host: {appConfig.Database.Host}");
Console.WriteLine($"Password: {appConfig.Database.Password}"); // Shows "REDACTED"
Memory Safety
What This Library Guarantees
✅ All JSON content stored as byte[] (never string)
✅ Dispose() immediately zeros all byte arrays via CryptographicOperations.ZeroMemory()
✅ Disposal cascades - parent disposal zeroes all children recursively
✅ Pooled buffers zeroed before return to ArrayPool
✅ Property removal zeros property name bytes
✅ Transform operations zero original values (even if transform throws)
What You Must Do
❌ Never use UTF-8 literals for sensitive data - literals are embedded in assembly and can't be zeroed
✅ Always dispose - use using blocks or explicit Dispose() calls
✅ Zero output buffers - if using ToUtf8Bytes(), you must zero the returned array
✅ Use UTF-8 APIs - prefer GetByPathUtf8 over GetByPath to avoid string allocations
Limitations
- Data is zeroed after disposal, not during active processing
- Brief processing window (milliseconds) where data exists in memory
- OS-level risks (page file, hibernation) if system events occur during processing
- See docs/security-implementation.md for complete threat model
Documentation
- Usage Guide - Detailed examples, patterns, stream parsing, transforms, POCO integration
- API Reference - Complete API documentation with all methods and properties
- Advanced Topics - Memory management deep dive, concurrency, performance tuning, architecture
- Performance Benchmarks - Json.Zero vs System.Text.Json comparison, trade-offs, real-world scenarios
- Security Implementation - Security guarantees, resource limits, threat model, best practices
Installation
dotnet add package Cocoar.Json.Zero
Requirements
- .NET 8.0 or later
- System.Text.Json (built-in)
Building & Testing
# Build
dotnet build src/Cocoar.Json.Zero.slnx
# Run tests (84 tests including security regression tests)
dotnet test src/tests/Cocoar.Json.Zero.Tests/
# Run benchmarks
dotnet run -c Release --project src/Benchmarks/Cocoar.Json.Zero.Benchmarks/
License
Apache License 2.0 - see LICENSE
Contributing
See CONTRIBUTING.md for development setup and guidelines.
Security
- Report vulnerabilities: See SECURITY.md
- Implementation details: See docs/security-implementation.md
Acknowledgments
Built on top of the excellent System.Text.Json library from the .NET team.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|
Full release notes: https://github.com/cocoar-dev/Cocoar.Json.Zero/blob/develop/CHANGELOG.md