Skip to main content

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
ParameterDefaultEffect
$withReferencestrueHandle circular references during cloning
$withRefSymbolstrueProcess $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​

OperationJsonUtilsVanillaRecommendation
Deep mergeJsonUtils.Extend()—Always JsonUtils — no vanilla equivalent
Shallow mergeJsonUtils.Extend(){ ...a, ...b }Spread for simple cases, Extend for safety
Shallow cloneJsonUtils.Clone(){ ...obj }Either works for simple objects
Deep cloneJsonUtils.DeepClone()structuredClone()JsonUtils for circular refs; structuredClone is fine too
Sort keysJsonUtils.Sort()Manual recursive sortJsonUtils — handles nesting
Pretty printJsonUtils.ToString()JSON.stringify(v, null, 2)JsonUtils for HTML output; vanilla for plain
Binary serializeJsonUtils.ToBuffer()TextEncoder + JSON.stringifyJsonUtils — handles special types
Key insight

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