Standard JSON.stringify produces different output for objects with the same data but different key orders. These packages ensure consistent output order, which is critical for generating cache keys, hashing data structures, or comparing objects. They differ mainly in performance, safety features like circular reference handling, and maintenance status.
When building frontend applications that rely on caching, memoization, or data hashing, object key order matters. Standard JSON.stringify does not guarantee property order, meaning two objects with identical data can produce different strings if keys were added in a different sequence. This breaks cache hits and causes false negatives in comparison logic. The packages fast-stable-stringify, json-stable-stringify, and safe-stable-stringify solve this by sorting keys deterministically. However, they differ significantly in how they handle edge cases like circular references and overall maintenance status.
All three packages ensure that object keys are sorted alphabetically before serialization. This guarantees that { b: 1, a: 2 } and { a: 2, b: 1 } produce the exact same string.
json-stable-stringify sorts keys recursively using a stable algorithm.
const stringify = require('json-stable-stringify');
const obj = { z: 1, a: 2 };
console.log(stringify(obj)); // "{\"a\":2,\"z\":1}"
fast-stable-stringify prioritizes speed while maintaining key order.
const stringify = require('fast-stable-stringify');
const obj = { z: 1, a: 2 };
console.log(stringify(obj)); // "{\"a\":2,\"z\":1}"
safe-stable-stringify also sorts keys but adds safety checks for circular structures.
const stringify = require('safe-stable-stringify');
const obj = { z: 1, a: 2 };
console.log(stringify(obj)); // "{\"a\":2,\"z\":1}"
This is the most critical architectural difference. Real-world state objects often contain circular references (e.g., parent-child DOM nodes or linked data structures). Standard JSON.stringify throws an error on circular refs. Two of these packages do the same, while one handles it gracefully.
json-stable-stringify throws a TypeError if it encounters a circular reference.
const stringify = require('json-stable-stringify');
const obj = { name: 'test' };
obj.self = obj; // Create circular ref
try {
console.log(stringify(obj)); // Throws TypeError
} catch (e) {
console.error('Failed to stringify');
}
fast-stable-stringify also throws on circular references to maintain speed.
const stringify = require('fast-stable-stringify');
const obj = { name: 'test' };
obj.self = obj; // Create circular ref
try {
console.log(stringify(obj)); // Throws TypeError
} catch (e) {
console.error('Failed to stringify');
}
safe-stable-stringify detects circular references and replaces them with a placeholder string instead of crashing.
const stringify = require('safe-stable-stringify');
const obj = { name: 'test' };
obj.self = obj; // Create circular ref
console.log(stringify(obj)); // "{\"name\":\"test\",\"self\":\"[Circular]\"}"
Performance varies based on V8 engine optimizations and package age. While fast-stable-stringify was built for speed, modern engines have narrowed the gap, and safe-stable-stringify is often comparable or faster in real-world scenarios due to better optimization paths.
json-stable-stringify is the oldest of the three. It is stable but receives fewer updates. It relies on older JavaScript patterns that may not leverage modern engine optimizations fully.
// Usage remains simple, but underlying logic is legacy
const stringify = require('json-stable-stringify');
const result = stringify(largeObject);
fast-stable-stringify uses low-level optimizations. It is very fast on flat structures but lacks safety guards.
// Optimized for raw throughput
const stringify = require('fast-stable-stringify');
const result = stringify(largeObject);
safe-stable-stringify is actively maintained and widely adopted in logging libraries like pino. It balances speed with safety, making it the modern standard.
// Actively maintained and optimized for modern runtimes
const stringify = require('safe-stable-stringify');
const result = stringify(largeObject);
| Feature | json-stable-stringify | fast-stable-stringify | safe-stable-stringify |
|---|---|---|---|
| Key Ordering | ✅ Yes | ✅ Yes | ✅ Yes |
| Circular Refs | ❌ Throws | ❌ Throws | ✅ Safe ([Circular]) |
| Maintenance | ⚠️ Legacy | ✅ Active | ✅ Active |
| Primary Focus | Stability | Speed | Safety + Speed |
| Recommendation | Legacy Only | Niche Use | Default Choice |
For new projects, safe-stable-stringify is the clear winner. It removes the risk of runtime crashes due to circular references without sacrificing performance. The other two packages require you to guarantee data structure safety manually, which adds cognitive load and potential bugs.
Use json-stable-stringify only if you are maintaining an older codebase that already depends on it. Use fast-stable-stringify only if you have profiled your application and proven that circular references are impossible and you need every microsecond of performance.
In most frontend architectures — especially when dealing with complex state management or caching layers — safety and reliability outweigh marginal speed gains. Choose the tool that prevents crashes first.
Choose this only if you have verified benchmarks showing a need for its specific implementation speed and you guarantee no circular references exist. It is less robust than the safe alternative regarding edge cases. Use it when raw throughput is the only metric that matters in a controlled environment.
Choose this only for maintaining legacy systems that already depend on it. It is older and lacks safety features found in newer packages, making it unsuitable for new architectural decisions. Consider migrating to a modern alternative if starting fresh.
Choose this for most modern applications as the default option. It handles circular references safely without throwing errors and offers top-tier performance. It is the safest choice for caching logic where data structure depth is unknown.
Notice: The License of this repository has been changed from GPL-3.0 to MIT as of 2017-08-25. All following commits will fall under the MIT license.
The test only succeeds when mine is faster than substack's in a particular browser.
The most popular repository providing this feature is substack's json-stable-stringify. The intent if this library is to provide a faster alternative for when performance is more important than features. It assumes you provide basic javascript values without circular references, and returns a non-indented string.
It currently offers a performance boost in popular browsers of about 40%. See the comparsion table below.
Usage:
var stringify = require('fast-stable-stringify');
stringify({ d: 0, c: 1, a: 2, b: 3, e: 4 }); // '{"a":2,"b":3,"c":1,"d":0,"e":4}'
Just like substack's, it does:
JSON.stringifyUnlike substack's, it does:
Tested validity (answer equal to substack's) and benchmark (faster than substack's). A test passes only if it has the same output as substack's but is faster (as concluded by benchmark.js).
To (hopefully) prevent certain smart browsers from concluding the stringification is not necessary because it is never used anywhere, I summed up all the lengths of the resulting strings of each benchmark contestant and printed it along with the result data.
See caniuse browser usage for the 'most popular browsers'.
| Suite | Browser | JSON.stringify@native | fast-stable-stringify@a9f81e8 | json-stable-stringify@1.0.1 | faster-stable-stringify@1.0.0 |
|---|---|---|---|---|---|
| libs | Chrome 60.0.3112 (Windows 7 0.0.0) | 414.45% (±2.67%) | *146.41% (±1.74%) | 100.00% (±1.11%) | 111.26% (±1.24%) |
| libs | Chrome Mobile 55.0.2883 (Android 6.0.0) | 495.16% (±16.9%) | *162.18% (±3.59%) | 100.00% (±5.44%) | 129.66% (±3.20%) |
| libs | Edge 14.14393.0 (Windows 10 0.0.0) | 487.88% (±11.9%) | *138.69% (±2.27%) | 100.00% (±1.51%) | 113.19% (±1.56%) |
| libs | Firefox 54.0.0 (Windows 7 0.0.0) | 530.66% (±17.6%) | *169.34% (±2.38%) | 100.00% (±1.68%) | 152.30% (±2.48%) |
| libs | IE 10.0.0 (Windows 7 0.0.0) | 427.80% (±10.9%) | *183.26% (±3.02%) | 100.00% (±2.42%) | ? |
| libs | IE 11.0.0 (Windows 7 0.0.0) | 298.01% (±4.35%) | *136.25% (±1.89%) | 100.00% (±1.73%) | ? |
| libs | IE 9.0.0 (Windows 7 0.0.0) | 316.18% (±4.01%) | *170.14% (±2.30%) | 100.00% (±1.52%) | ? |
| libs | Mobile Safari 10.0.0 (iOS 10.3.0) | 554.98% (±23.7%) | *115.33% (±4.23%) | 100.00% (±3.11%) | *118.66% (±2.96%) |
| libs | Safari 10.0.1 (Mac OS X 10.12.1) | 722.94% (±24.8%) | *119.49% (±3.62%) | 100.00% (±2.12%) | 106.06% (±3.12%) |
Click the build status badge to view the original output.
Disclaimer: the more I test the more I realize how many factors actually affect the outcome. Not only the browser and browser version, but particularly the json content and a random factor in every test run. Outcomes may sometimes vary more than 10% between tests, despite my attempt to reduce this by increasing the sample size to 2.5 times. Nevertheless, the overall picture typically still holds.
JSON.stringify has been added for reference. In general, aside from the cases where you need the guarantee of a stable result, I would recommend against using this library, or any stable stringification library for that matter.
faster-stable-stringify has been added for comparison. It seems it does not work in IE 9 to 11 without a polyfill solution for WeakMap. It is displayed as somewhat slower, but as I decided on the JSON input to test and develop against, this result may be somewhat biased. In some test runs, faster-stable-stringify will be faster in a browser. In fact, it is consistently somewhat faster in the Mobile Safari 10.
The original implementation was with regexp. A pull request showed a literal string approach that was faster in some browsers. Other libraries would just use JSON.stringify. After a speed comparison between all three methods, it became clear that native JSON.stringify is generally much faster in string escaping than both these methods, even in the IE legacy browsers. The browsers that do not have JSON.stringify are not offered for test automation, and when looking at the usage percentage of such browsers I no longer see a reason to not use JSON.stringify.
Therefore, the exposed regexp and string are removed from the API. If you still need this functionality, I simply encourage you to use JSON.stringify.
It runs karma-benchmark tests now. For testing in node, do:
npm test
This requires saucelabs credentials in your env. That, or edit the karma.conf.js to your liking.
Running this test will cause files in ./results/libs/ to update. Run npm run table to get a pretty md table of the results.