Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

stable-stringify

CI

Deterministic JSON.stringify for TypeScript/JavaScript: recursively sorted object keys, cycle detection, key filtering/replacer, stable number formatting, a streaming generator API for huge objects, and a hashObject(obj) helper. Zero runtime dependencies (uses Node's built-in crypto for hashing only).

Why

Regular JSON.stringify preserves insertion order, so two objects with the same data but different key order produce different strings — useless as a cache key, signature, or dedup fingerprint. stable-stringify sorts keys at every level so equivalent objects always serialize identically.

Install

npm install stable-stringify

Quickstart

import { stableStringify, hashObject } from "stable-stringify";

stableStringify({ b: 2, a: 1 }); // '{"a":1,"b":2}'
stableStringify({ a: 1, b: 2 }); // '{"a":1,"b":2}' — identical regardless of input order

hashObject({ b: 2, a: 1 }); // sha256 hex digest, same for any key-order permutation

API

stableStringify(value: unknown, options?: StringifyOptions): string | undefined

Deterministic stringify. Returns undefined for the same top-level cases JSON.stringify does (undefined, a function, a symbol).

stableStringifyChunks(value: unknown, options?: StringifyOptions): Generator<string>

Same serialization, yielded incrementally instead of built into one string — for piping/consuming a huge object's output without holding it all in memory at once.

hashObject(value: unknown, options?: StringifyOptions): string

SHA-256 hex digest of stableStringify(value, options) (via Node's crypto module).

formatNumber(n: number): string

The number formatter used internally: NaN/Infinity/-Infinity"null" (matches JSON.stringify), -0"0", otherwise String(n).

interface StringifyOptions {
  replacer?: (key: string, value: unknown) => unknown; // called after toJSON(), like JSON.stringify's function replacer
  keys?: readonly string[];      // allow-list of keys to include, at every level
  cycles?: "error" | "placeholder"; // default "error"
  cyclePlaceholder?: string;     // default "[Circular]"
}

Limits

  • No space/pretty-print option — output is always compact, since the point is a stable machine-comparable string, not human formatting.
  • keys filters every object level identically (same semantics as JSON.stringify's array-form replacer) — there's no per-path filtering.
  • Cycle detection tracks the current recursion path (ancestors), not the whole object graph — a value referenced twice from siblings (not an ancestor) is serialized twice, which is correct JSON semantics, not a cycle.
  • hashObject hashes the stringified form; two semantically-equal values that stringify differently (e.g. a Map vs a plain object) will hash differently.

Part of the ferrow-toolkit collection · Sponsored by Ferrow

About

Deterministic JSON.stringify: sorted keys, cycle detection, streaming generator API, hashObject sha256 helper

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages