JsonUtils
JsonUtils provides safe object manipulation — deep merging, cloning, JSONP generation, binary serialization, and more. Use Extend() instead of Object.assign or spread for deep merges.
Package: io-oidis-commons
import { JsonUtils } from "@io-oidis-commons/.../Utils/JsonUtils.js";
Extend — Deep Merge
JsonUtils.Extend($parent : any, $child : any) : any
Performs a deep merge of $child into $parent. Unlike Object.assign or spread (...), Extend recursively merges nested objects instead of overwriting them.
const defaults = {
offset: 0,
limit: 50,
filter: { deleted: { $ne: true } },
sort: { created: -1 }
};
const userOptions = {
limit: 25,
filter: { status: "active" }
};
const options = JsonUtils.Extend(defaults, userOptions);
// Result:
// {
// offset: 0,
// limit: 25,
// filter: { deleted: { $ne: true }, status: "active" }, ← deep merged!
// sort: { created: -1 }
// }
Compare with vanilla approaches:
// ❌ Object.assign — shallow, overwrites nested objects
Object.assign({}, defaults, userOptions);
// filter would be { status: "active" } — deleted check is lost!
// ❌ Spread — same shallow problem
{ ...defaults, ...userOptions };
// filter would be { status: "active" } — deleted check is lost!
// ✅ JsonUtils.Extend — deep merge preserves nested properties
JsonUtils.Extend(defaults, userOptions);
// filter is { deleted: { $ne: true }, status: "active" } — both preserved
Common Pattern: Options with Defaults
@Extern()
public async ListRecords($options : IListOptions) : Promise<IModelListResult> {
const options : IListOptions = JsonUtils.Extend({limit: 50, offset: 0}, $options);
const filter : any = {deleted: {$ne: true}};
const size : number = await MyRecord.Model().countDocuments(filter);
const records : MyRecord[] = await MyRecord.Find(filter, {limit: options.limit, offset: options.offset});
// ...
}
Clone & DeepClone
JsonUtils.Clone($source : any) : any
JsonUtils.DeepClone($source : any, $withReferences? : boolean, $withRefSymbols? : boolean) : any
Create copies of objects:
// Shallow clone (JSON.parse/stringify based)
const copy = JsonUtils.Clone(original);
// Deep clone (recursive, handles references)
const deepCopy = JsonUtils.DeepClone(original);
DeepClone Reference Modes
DeepClone has two optional parameters that control how circular references and $ref symbols are handled:
// Default: preserve references and ref symbols
JsonUtils.DeepClone(source); // $withReferences=true, $withRefSymbols=true
// Clone without resolving references (fastest, but circular refs cause issues)
JsonUtils.DeepClone(source, false); // $withReferences=false
// Clone with references but without $ref symbol processing
JsonUtils.DeepClone(source, true, false); // $withReferences=true, $withRefSymbols=false
| Parameter | Default | Effect |
|---|---|---|
$withReferences | true | Handle circular references during cloning |
$withRefSymbols | true | Process $ref symbols in the cloned output |
ParseRefSymbols
JsonUtils.ParseRefSymbols($source : any) : any
Resolves $ref symbols in a data object — the inverse operation of DeepClone with symbol mode. Used when processing JSONP configuration files that use $ref for cross-references:
const config = {
database: { host: "localhost", port: 27017 },
cache: { $ref: "#/database" } // Reference to database config
};
const resolved = JsonUtils.ParseRefSymbols(config);
// resolved.cache === resolved.database (same reference)
ToJsonp — JSONP Format Generation
JsonUtils.ToJsonp($source : any, $interface? : IClassName, $imports? : IClassName[]) : string
Converts a JSON object to JSONP format string — the configuration file format used by Oidis (.jsonp files). Optionally includes TypeScript interface typing and import statements:
const config = { name: "MyProject", version: "1.0.0" };
// Basic JSONP output
JsonUtils.ToJsonp(config);
// With interface and imports
JsonUtils.ToJsonp(config, MyInterface, [Dependency1, Dependency2]);
ToString — Pretty Print
JsonUtils.ToString($value : any, $prefix? : string, $htmlTag? : boolean) : string
Returns a formatted string representation of a JSON object, suitable for debug output:
// Pretty-print to HTML (default)
const htmlOutput = JsonUtils.ToString(data);
// Pretty-print to plain text
const plainOutput = JsonUtils.ToString(data, "", false);
// With indentation prefix
const indented = JsonUtils.ToString(data, " ");
Sort — Recursive Key Sorting
JsonUtils.Sort($value : any) : any
Returns a new object with keys sorted alphabetically at every nesting level. Arrays are left unchanged:
const unsorted = { zebra: 1, alpha: 2, nested: { beta: 3, alpha: 4 } };
const sorted = JsonUtils.Sort(unsorted);
// → { alpha: 2, nested: { alpha: 4, beta: 3 }, zebra: 1 }
Useful for generating deterministic output (e.g., comparing configurations, stable serialization).
ToBuffer & ToJson — Binary Serialization
JsonUtils.ToBuffer($input : any) : ArrayBuffer
JsonUtils.ToJson($input : ArrayBuffer) : any
Serialize JSON objects to/from ArrayBuffer for efficient binary transport (worker communication, IPC):
// Serialize to binary
const buffer = JsonUtils.ToBuffer({ action: "process", data: largeDataSet });
// Send via worker/IPC
worker.postMessage(buffer, [buffer]);
// Deserialize on the other side
const data = JsonUtils.ToJson(receivedBuffer);
When to Use JsonUtils vs Vanilla
| Operation | JsonUtils | Vanilla | Recommendation |
|---|---|---|---|
| Deep merge | JsonUtils.Extend() | — | Always JsonUtils — no vanilla equivalent |
| Shallow merge | JsonUtils.Extend() | { ...a, ...b } | Spread for simple cases, Extend for safety |
| Shallow clone | JsonUtils.Clone() | { ...obj } | Either works for simple objects |
| Deep clone | JsonUtils.DeepClone() | structuredClone() | JsonUtils for circular refs; structuredClone is fine too |
| Sort keys | JsonUtils.Sort() | Manual recursive sort | JsonUtils — handles nesting |
| Pretty print | JsonUtils.ToString() | JSON.stringify(v, null, 2) | JsonUtils for HTML output; vanilla for plain |
| Binary serialize | JsonUtils.ToBuffer() | TextEncoder + JSON.stringify | JsonUtils — handles special types |
The main reason to use JsonUtils.Extend() over spread/Object.assign is deep merging. If you're just doing shallow property assignment on flat objects, spread is fine. But when objects have nested structures (filters, options, configs), always use Extend().
See also: Utilities Overview | ObjectValidator | StringUtils | Logging