json-stable-stringify
A deterministic version of JSON.stringify() that sorts object keys so identical data always produces identical output.
Repository Health
Technical Analysis
json-stable-stringify is a drop-in replacement for JSON.stringify() that guarantees the same input object always produces the exact same output string, regardless of the order keys were set on it. It does this by sorting object keys (alphabetically by default, or via a custom comparator) before serializing, which makes the resulting string safe to use for hashing, caching, diffing, or equality checks — none of which JSON.stringify() can guarantee on its own since JavaScript object key order isn’t part of the JSON contract.
Beyond the default sort, the library accepts a custom cmp function so callers can order keys by value, by a secondary lookup via the provided get accessor, or any other rule, and supports space for pretty-printing, a replacer for transforming values before they’re serialized, and a cycles option to safely stringify circular structures instead of throwing. Internally it also guards against prototype pollution and monkey-patched built-ins by binding directly to Array.prototype methods via call-bind/call-bound rather than trusting whatever methods happen to be on the object at call time.
Maintained by Jordan Harband (ljharb) as part of a large family of small, dependency-audited utility packages, it targets very old Node.js engines (>= 0.4) and ships a hand-written index.d.ts for TypeScript consumers alongside a plain-JavaScript implementation, favoring maximal runtime compatibility over modern build tooling.
What You Get
- A single
stringify(obj, opts)function that mirrorsJSON.stringify()’s call shape but sorts object keys before serializing - A custom
cmp(a, b, get)comparator option to control key order by key name, by value, or via a secondary lookup on sibling keys - A
cyclesoption that serializes circular references as"__cycle__"instead of throwing, alongside the default throw-on-cycle behavior matching native JSON.stringify - A
replaceroption compatible with the same signature as the nativeJSON.stringifyreplacer parameter - A
spaceoption for indentation/pretty-printing, accepting either a string or a number of spaces - Hand-written TypeScript declarations (
index.d.ts) shipped alongside the plain-JS implementation
Common Use Cases
- Generating a stable cache key or content hash from an object whose key insertion order may vary between calls
- Producing deterministic snapshots or fixtures in tests where JSON key order would otherwise cause spurious diffs
- Comparing two objects for deep equality by stringifying both and comparing the resulting strings
- Serializing request/response payloads for signature generation (e.g. webhook signing, request signing) where the signer and verifier must produce identical bytes
- Safely stringifying objects that may contain circular references without crashing the calling code
Under The Hood
Architecture
The entire library is a single exported function, stableStringify, implemented as one recursive closure in index.js that walks the input value depth-first: primitives are delegated straight to the platform’s own JSON.stringify (or a jsonify polyfill on environments without a native JSON), arrays are mapped element-by-element, and plain objects have their keys pulled via object-keys, sorted (alphabetically, or via the caller’s cmp), and then recursively stringified. Cycle detection is handled with a seen array pushed onto and spliced from as the walk descends and returns, and a replacer/toJSON hook is applied to every node before it’s classified — a small, single-purpose design appropriate for a function with one job and no need for internal layering.
Tech Stack
Plain, dependency-injected JavaScript with no build step: runtime dependencies are isarray and object-keys (ES5-era shims for behavior later standardized), jsonify (a JSON.stringify/parse polyfill for pre-ES5 environments), and call-bind/call-bound, which bind directly to intrinsic prototype methods so the library’s behavior can’t be altered by code that monkey-patches Array.prototype. Type checking is done via a hand-authored index.d.ts validated against the implementation using tsc and @arethetypeswrong/cli in the lint step, rather than compiling from TypeScript source. CI runs across dedicated GitHub Actions workflows split by Node.js era (node-aught, node-tens, node-twenties), reflecting the package’s stated support back to Node >= 0.4.
Code Quality
Tests use tape and are organized by concern — comparator behavior, nested/cyclic structures, the replacer option, space/indentation, plain string handling, and toJSON support — giving reasonable behavioral coverage for a library this size. Linting runs via the strict, shared @ljharb/eslint-config ahead of every test run (pretest invokes lint), and a postlint step further checks the shipped type declarations against the runtime implementation. Defensive coding is evident throughout: explicit TypeErrors for invalid options, use of bound intrinsics instead of trusting the ambient environment, and JSDoc type annotations on nearly every internal variable despite there being no TypeScript build.
What Makes It Unique
Deterministic, key-sorted JSON serialization is a well-established pattern with several comparable packages in the ecosystem; this implementation’s specific contribution is defensive robustness — binding to intrinsics rather than ambient prototype methods to resist prototype pollution and monkey-patching — combined with a comparator API that exposes a get accessor for sibling-key lookups, letting callers make ordering decisions that depend on more than just the two keys being compared.
Used by 5 apps in this directory
AFFiNE
Productivity · Project Management · Note Taking
Write, draw, and plan in one infinite canvas — the open-source alternative to Notion and Miro that keeps your data yours.
Dittofeed
Marketing · Automation
Open-source omni-channel customer engagement platform for automating transactional and marketing messages via email, SMS, WhatsApp, Slack, and mobile push.
Pezzo
AI Development · Monitoring
Open-source LLMOps platform for prompt management, AI observability, intelligent caching, and real-time cost tracking across LLM providers.
Sanity
CMS
Open-source headless CMS with a fully customizable React Studio, real-time collaborative editing, structured content modeling, and GROQ query language
Trigger.dev
Automation · AI Development · Developer Tools
Build and deploy fully-managed AI agents and background jobs in TypeScript — with no timeouts, durable retries, real-time observability, and elastic scaling built in.