json-stable-stringify

A deterministic version of JSON.stringify() that sorts object keys so identical data always produces identical output.

Library
npm
v1.3.0
79stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
29/100Needs Attention
Development Activity0
Maintenance0
Community44
Maturity52
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
63/100Good
Architecture78
Code Quality80
Innovation40
Learning Curve55

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 mirrors JSON.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 cycles option that serializes circular references as "__cycle__" instead of throwing, alongside the default throw-on-cycle behavior matching native JSON.stringify
  • A replacer option compatible with the same signature as the native JSON.stringify replacer parameter
  • A space option 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

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search