json-stable-stringify-without-jsonify vs json-stable-stringify
Deterministic JSON Serialization Libraries
json-stable-stringify-without-jsonifyjson-stable-stringify
Deterministic JSON Serialization Libraries

json-stable-stringify and json-stable-stringify-without-jsonify are both npm packages designed to provide deterministic JSON serialization by sorting object keys alphabetically before stringification. This ensures consistent output regardless of property insertion order, which is critical for generating cache keys, creating digital signatures, or comparing objects. json-stable-stringify uses the jsonify package internally to handle edge cases like circular references and non-JSON-safe values, while json-stable-stringify-without-jsonify avoids this dependency entirely, offering a lighter but less robust alternative that only works reliably with plain JSON-compatible data.

Npm Package Weekly Downloads Trend
3 Years
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
json-stable-stringify-without-jsonify56,797,60513-19 years agoMIT
json-stable-stringify8,583,7717636.4 kB79 months agoMIT

Deterministic JSON Serialization: json-stable-stringify vs json-stable-stringify-without-jsonify

Both json-stable-stringify and json-stable-stringify-without-jsonify solve a common but subtle problem in JavaScript applications: producing consistent, predictable JSON output regardless of property insertion order. This matters when you need to compare objects, generate cache keys, or create digital signatures — situations where { a: 1, b: 2 } and { b: 2, a: 1 } must serialize identically.

However, these packages differ significantly in implementation strategy, dependencies, and compatibility. Let’s break down what each offers and where they diverge.

🔍 Core Purpose: Why Stable Stringification Matters

Native JSON.stringify() does not guarantee consistent key ordering across environments or even within the same runtime if object properties were added in different orders:

// Unreliable with native JSON.stringify
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 2, a: 1 };

console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // false in some engines

Stable stringifiers sort object keys alphabetically before serialization, ensuring deterministic output:

// Both packages produce this consistently:
'{"a":1,"b":2}'

This is essential for:

  • Generating reliable cache keys
  • Creating content hashes or digital signatures
  • Comparing configuration objects
  • Debugging state diffs in Redux-like systems

⚙️ Implementation Strategy: Dependency on jsonify

The key difference lies in how each package handles circular references and complex data types.

json-stable-stringify uses the jsonify package internally to safely handle edge cases like:

  • Circular references
  • Undefined values
  • Functions
  • Non-enumerable properties
// json-stable-stringify
import stableStringify from 'json-stable-stringify';

const obj = { b: 2, a: 1 };
console.log(stableStringify(obj)); // '{"a":1,"b":2}'

// Handles circular refs gracefully
const circular = { a: 1 };
circular.self = circular;
console.log(stableStringify(circular)); // Throws or handles per options

json-stable-stringify-without-jsonify avoids the jsonify dependency entirely, using a simpler recursive approach that only supports plain JSON-compatible data (objects, arrays, strings, numbers, booleans, null). It will fail or behave unpredictably with:

  • Functions
  • Undefined values
  • Circular structures
  • Symbols
// json-stable-stringify-without-jsonify
import stableStringify from 'json-stable-stringify-without-jsonify';

const obj = { b: 2, a: 1 };
console.log(stableStringify(obj)); // '{"a":1,"b":2}'

// Will crash on circular refs
const circular = { a: 1 };
circular.self = circular;
// stableStringify(circular); // Maximum call stack size exceeded

📦 Dependency Footprint and Compatibility

json-stable-stringify pulls in jsonify as a transitive dependency. While jsonify is a small, well-maintained package, this adds an extra layer to your dependency tree. However, it ensures robust handling of non-standard inputs.

json-stable-stringify-without-jsonify has zero dependencies, making it ideal for:

  • Minimal bundle size requirements
  • Environments where dependency constraints are strict
  • Cases where you control input data and know it’s strictly JSON-safe

🛠️ API and Options Support

Both packages support identical core APIs and options:

// Common options supported by both
stableStringify(obj, {
  space: 2,           // indentation
  cmp: (a, b) => a.key < b.key ? -1 : 1, // custom sort
  replacer: (key, val) => /* transform values */
});

However, only json-stable-stringify properly respects the replacer function for non-JSON-safe values because of its jsonify foundation. The -without-jsonify variant may ignore or mishandle replacer logic when encountering functions or undefined.

🧪 Real-World Usage Scenarios

Scenario 1: Generating Cache Keys from Config Objects

You’re building a build tool that caches transformation results based on config hashes.

  • Best choice: json-stable-stringify-without-jsonify
  • Why? Your configs are guaranteed to be plain objects with no functions or circular refs. Zero deps = smaller install footprint.
import stringify from 'json-stable-stringify-without-jsonify';
import crypto from 'crypto';

function getConfigHash(config) {
  return crypto.createHash('sha256')
    .update(stringify(config))
    .digest('hex');
}

Scenario 2: Serializing Redux State for Time-Travel Debugging

Your app stores complex state that might include non-serializable values during development.

  • Best choice: json-stable-stringify
  • Why? You need graceful handling of accidental non-JSON values (e.g., React components in state) without crashing.
import stableStringify from 'json-stable-stringify';

// Safely serialize even if state contains unexpected values
const snapshot = stableStringify(reduxState, {
  replacer: (key, value) => {
    if (typeof value === 'function') return '[Function]';
    return value;
  }
});

Scenario 3: Creating Digital Signatures for API Payloads

You’re signing payloads that must be strictly JSON-compliant per spec.

  • Either works, but prefer json-stable-stringify-without-jsonify
  • Why? Input validation should happen upstream; you want minimal, auditable code for the signing step.

⚠️ Critical Limitation of json-stable-stringify-without-jsonify

The -without-jsonify variant will crash on circular references due to infinite recursion. If there’s any chance your data might contain cycles (common in tree structures, DOM-like objects, or ORM models), do not use it.

// Dangerous with json-stable-stringify-without-jsonify
const node = { id: 1 };
node.children = [{ parent: node }]; // Circular!

// This will throw: RangeError: Maximum call stack size exceeded
stringify(node);

In contrast, json-stable-stringify detects cycles and throws a clear error (or handles them if you provide a custom replacer).

📌 Summary Table

Featurejson-stable-stringifyjson-stable-stringify-without-jsonify
DependenciesRequires jsonifyZero dependencies
Circular Reference HandlingDetects and errors gracefullyCrashes with stack overflow
Non-JSON Value SupportHandles via jsonify + replacerUnpredictable behavior
Bundle SizeSlightly largerMinimal
Input RequirementsForgivingStrictly JSON-safe data only

💡 Final Recommendation

  • Choose json-stable-stringify if you need robustness against malformed or complex inputs, especially in libraries or tools where you don’t control the data shape.

  • Choose json-stable-stringify-without-jsonify only if you have full control over input data (guaranteed JSON-safe, no cycles) and prioritize minimal dependencies.

Remember: deterministic output is useless if your app crashes during serialization. When in doubt, go with the more resilient option.

How to Choose: json-stable-stringify-without-jsonify vs json-stable-stringify
  • json-stable-stringify-without-jsonify:

    Choose json-stable-stringify-without-jsonify only when you have strict control over your input data and can guarantee it contains exclusively JSON-safe values (no functions, undefined, symbols, or circular structures). Its zero-dependency design makes it ideal for minimal bundle size requirements in applications like build tools or cryptographic signing where inputs are pre-validated and simplicity is prioritized over resilience.

  • json-stable-stringify:

    Choose json-stable-stringify when you need reliable handling of complex or untrusted input data that might contain circular references, functions, or other non-JSON-safe values. Its integration with jsonify provides graceful error handling and consistent behavior across edge cases, making it suitable for libraries, debugging tools, or any scenario where input validation isn't guaranteed. The slight increase in dependency footprint is justified by the robustness it offers.

README for json-stable-stringify-without-jsonify

json-stable-stringify

This is the same as https://github.com/substack/json-stable-stringify but it doesn't depend on libraries without licenses (jsonify).

deterministic version of JSON.stringify() so you can get a consistent hash from stringified results

You can also pass in a custom comparison function.

browser support

build status

example

var stringify = require('json-stable-stringify');
var obj = { c: 8, b: [{z:6,y:5,x:4},7], a: 3 };
console.log(stringify(obj));

output:

{"a":3,"b":[{"x":4,"y":5,"z":6},7],"c":8}

methods

var stringify = require('json-stable-stringify')

var str = stringify(obj, opts)

Return a deterministic stringified string str from the object obj.

options

cmp

If opts is given, you can supply an opts.cmp to have a custom comparison function for object keys. Your function opts.cmp is called with these parameters:

opts.cmp({ key: akey, value: avalue }, { key: bkey, value: bvalue })

For example, to sort on the object key names in reverse order you could write:

var stringify = require('json-stable-stringify');

var obj = { c: 8, b: [{z:6,y:5,x:4},7], a: 3 };
var s = stringify(obj, function (a, b) {
    return a.key < b.key ? 1 : -1;
});
console.log(s);

which results in the output string:

{"c":8,"b":[{"z":6,"y":5,"x":4},7],"a":3}

Or if you wanted to sort on the object values in reverse order, you could write:

var stringify = require('json-stable-stringify');

var obj = { d: 6, c: 5, b: [{z:3,y:2,x:1},9], a: 10 };
var s = stringify(obj, function (a, b) {
    return a.value < b.value ? 1 : -1;
});
console.log(s);

which outputs:

{"d":6,"c":5,"b":[{"z":3,"y":2,"x":1},9],"a":10}

space

If you specify opts.space, it will indent the output for pretty-printing. Valid values are strings (e.g. {space: \t}) or a number of spaces ({space: 3}).

For example:

var obj = { b: 1, a: { foo: 'bar', and: [1, 2, 3] } };
var s = stringify(obj, { space: '  ' });
console.log(s);

which outputs:

{
  "a": {
    "and": [
      1,
      2,
      3
    ],
    "foo": "bar"
  },
  "b": 1
}

replacer

The replacer parameter is a function opts.replacer(key, value) that behaves the same as the replacer from the core JSON object.

install

With npm do:

npm install json-stable-stringify

license

MIT