fastest-stable-stringify
fastest-stable-stringify serializes JSON-like JavaScript values after sorting every object's own enumerable keys lexicographically. Objects with the same data but different insertion order therefore produce the same compact string, which is useful for cache keys, hashes, snapshots, and reproducible files. Arrays keep their order and objects with `toJSON` use it. The package is a tiny dependency-free CommonJS function, but it intentionally offers fewer controls and safety features than newer stable stringifiers.
Retain it when its byte-for-byte output is already a compatibility contract. For a new project, safe-stable-stringify is the better default because speed is not worth silent option gaps and brittle values without a current workload benchmark.
Use it if
- You already depend on its exact lexicographic output and changing serialized bytes would invalidate caches, signatures, or snapshots
- Your inputs are acyclic, JSON-compatible objects and you only need compact deterministic output
- You need a zero-dependency CommonJS stringifier for an older Node application
- You have benchmarks on your own production-shaped values showing this implementation is materially faster than maintained alternatives
- You are choosing a stable stringifier today: 2.0.2 was published in 2018 and the repository's last push was in 2023, while safe-stable-stringify is maintained and handles more failure cases
- Your values may contain cycles or BigInt: cycles end in a maximum-call-stack RangeError and BigInt throws a TypeError
- You need custom key ordering, a replacer, pretty-print spacing, or cycle configuration: the exported function ignores extra arguments and exposes no options
- You use TypeScript or native ESM and want first-class packaging: the tarball has no declarations, exports map, or module entry
- You need a formal canonical JSON format for cross-language signatures: this sorts JavaScript keys but does not implement RFC 8785 number and string canonicalization rules
Setup reality
`npm install fastest-stable-stringify` adds a 15,717-byte package with no runtime dependencies, peer dependencies, native builds, credentials, or configuration. The documented interface is CommonJS: `const stringify = require('fastest-stable-stringify')`. ESM applications depend on Node's CommonJS default-import interoperability, and TypeScript projects need a local declaration or a third-party type package because 2.0.2 ships no types. There is exactly one supported argument despite example files in the tarball passing comparator callbacks; the implementation ignores those extra arguments and always sorts keys with JavaScript's default lexicographic `Array#sort`. Output is compact only, with no replacer or spacing option. The first-run surprises are input semantics. Circular references recurse until a RangeError instead of producing a helpful cycle error. BigInt and Symbol values can throw. Map and Set have no enumerable string keys and serialize as `{}` unless converted. Date works through `toJSON`, and any custom `toJSON` method can change the data before sorting. Like native JSON, undefined object properties are omitted, undefined array entries become null, and non-finite numbers become null. Unlike native `JSON.stringify`, a top-level undefined or function becomes the string `null` rather than returning undefined. Getters run during traversal, so serialization is not side-effect-free. The README's speed claim comes from its own undated benchmark output; rerun representative benchmarks on your current Node version before choosing it for performance. Finally, deterministic bytes are only useful if all callers use the same package version and preprocessing rules. Do not label its output canonical JSON in a protocol or use it for cross-language signatures without a formal canonicalization layer.
Patterns
Serialize an object deterministicallystringify-object
const stringify = require('fastest-stable-stringify');
const left = stringify({ b: 2, a: 1 });
const right = stringify({ a: 1, b: 2 });
console.log(left); // {"a":1,"b":2}
console.log(left === right); // trueObject keys are sorted lexicographically at every depth. Array item order is preserved.
Build a cache key from structured inputcreate-cache-key
const stringify = require('fastest-stable-stringify');
const { createHash } = require('node:crypto');
function cacheKey(namespace, input) {
const bytes = stringify(input);
const digest = createHash('sha256').update(bytes).digest('hex');
return `${namespace}:${digest}`;
}Validate inputs first. Cycles and BigInt throw, while Map and Set collapse to empty objects and can cause unintended key collisions.
Compare JSON-like values independent of key insertion ordercompare-json-values
const stringify = require('fastest-stable-stringify');
function sameJsonValue(a, b) {
return stringify(a) === stringify(b);
}
console.log(sameJsonValue({ x: 1, y: 2 }, { y: 2, x: 1 })); // trueThis is not general deep equality: undefined object fields disappear, NaN becomes null, prototypes are ignored, and toJSON can rewrite a value.
Write a reproducible compact JSON filewrite-reproducible-json
const stringify = require('fastest-stable-stringify');
const { writeFile } = require('node:fs/promises');
async function writeManifest(path, manifest) {
await writeFile(path, stringify(manifest) + '\n', 'utf8');
}There is no pretty-print option. Adding a final newline is caller policy and changes hashes compared with the returned string alone.
Sign a controlled JavaScript payloadsign-known-payload
const stringify = require('fastest-stable-stringify');
const { createHmac } = require('node:crypto');
function sign(payload, secret) {
return createHmac('sha256', secret)
.update(stringify(payload), 'utf8')
.digest('base64url');
}Only use this when signer and verifier share the exact JavaScript serializer contract. It is not RFC 8785 canonical JSON for cross-language protocols.
Serialize Date values through toJSONserialize-date
const stringify = require('fastest-stable-stringify');
const value = stringify({
createdAt: new Date('2026-08-08T12:00:00.000Z'),
});
// {"createdAt":"2026-08-08T12:00:00.000Z"}The serializer calls any object's toJSON method before sorting. User-defined toJSON can execute code or return unexpected shapes.
Convert a Map before serializationconvert-map
const stringify = require('fastest-stable-stringify');
const headers = new Map([
['content-type', 'application/json'],
['accept', 'application/json'],
]);
const json = stringify(Object.fromEntries(headers));Map and Set instances have no enumerable string keys and otherwise serialize as `{}`. Decide how non-string Map keys should be represented.
Convert BigInt values explicitlyencode-bigint
const stringify = require('fastest-stable-stringify');
const record = { id: 9007199254740993n, count: 2n };
const jsonReady = Object.fromEntries(
Object.entries(record).map(([key, value]) => [
key,
typeof value === 'bigint' ? value.toString() : value,
]),
);
const json = stringify(jsonReady);BigInt throws in version 2.0.2. A shallow conversion like this does not handle nested values; use a recursive schema-aware conversion there.
Detect cycles before calling the serializerreject-circular-input
const stringify = require('fastest-stable-stringify');
function assertAcyclic(value, seen = new WeakSet()) {
if (!value || typeof value !== 'object') return;
if (seen.has(value)) throw new TypeError('Circular value');
seen.add(value);
for (const child of Object.values(value)) assertAcyclic(child, seen);
seen.delete(value);
}
assertAcyclic(input);
const json = stringify(input);The package has no cycle option and eventually throws a maximum-call-stack RangeError. This helper permits repeated non-cyclic references.
Make undefined handling explicitpreserve-undefined-policy
const stringify = require('fastest-stable-stringify');
const objectJson = stringify({ present: 1, missing: undefined });
const arrayJson = stringify([1, undefined]);
console.log(objectJson); // {"present":1}
console.log(arrayJson); // [1,null]Top-level undefined is different again: this package returns the string `null`, while native JSON.stringify returns undefined.
Load the CommonJS package from Node ESMimport-from-esm
import stringify from 'fastest-stable-stringify';
const value = stringify({ z: 3, a: 1 });
console.log(value);This relies on Node's CommonJS default-import interop. The package has no ESM build or exports map, so confirm behavior in your bundler.
Add a minimal local TypeScript declarationdeclare-types-locally
// fastest-stable-stringify.d.ts
declare module 'fastest-stable-stringify' {
function stringify(value: unknown): string;
export = stringify;
}The package ships no declarations. The return type is always string in this implementation, including the surprising top-level undefined case.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| safe-stable-stringify | npm | You need maintained deterministic serialization with BigInt, circular-value handling, replacers, and configuration |
| fast-json-stable-stringify | npm | You want the widely used small implementation with a documented comparator and optional cycle handling |
| json-stable-stringify | npm | You need custom comparison and replacer functions and accept a somewhat larger implementation |