fast-safe-stringify vs json-stringify-safe vs safe-json-stringify
Safe JSON Stringification Libraries for JavaScript
fast-safe-stringifyjson-stringify-safesafe-json-stringifySimilar Packages:

Safe JSON Stringification Libraries for JavaScript

fast-safe-stringify, json-stringify-safe, and safe-json-stringify are npm packages designed to safely serialize JavaScript objects that may contain circular references, functions, undefined, or other values that cause native JSON.stringify() to fail or behave unexpectedly. They provide drop-in replacements that avoid throwing errors and instead produce informative placeholder strings for non-serializable content, making them essential for logging, debugging, and handling untrusted data structures in production applications.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
fast-safe-stringify0357-95 years agoMIT
json-stringify-safe0555-711 years agoISC
safe-json-stringify058-38 years agoMIT

Safe JSON Stringification in JavaScript: fast-safe-stringify vs json-stringify-safe vs safe-json-stringify

When building robust frontend or Node.js applications, you’ll eventually need to serialize JavaScript objects that might contain circular references, functions, undefined, or other non-serializable values. Native JSON.stringify() throws an error on circular structures and silently drops undefined and functions — which can lead to unexpected behavior or crashes in production. The three packages under review—fast-safe-stringify, json-stringify-safe, and safe-json-stringify—all aim to solve this problem, but they differ significantly in performance, API design, maintenance status, and real-world reliability.

⚠️ Deprecation Status: One Package Is Officially Retired

Before diving into features, it’s critical to address project health:

  • json-stringify-safe is officially deprecated. Its npm page states: "This module is no longer maintained. Please use fast-safe-stringify instead." It hasn’t seen a meaningful update since 2016 and contains known bugs with deeply nested circular structures.
  • safe-json-stringify appears unmaintained. The GitHub repository has been archived, and the last npm publish was in 2017. No official deprecation notice exists, but active development has ceased.
  • fast-safe-stringify is actively maintained, widely adopted in production systems (including by Pino logging), and regularly updated with performance improvements and bug fixes.

🛑 Bottom line: Avoid json-stringify-safe and safe-json-stringify in new projects. Only consider them for legacy compatibility.

🧪 Core Behavior: How Each Handles Problematic Values

All three libraries attempt to avoid throwing errors when encountering circular references or non-serializable types, but their strategies differ.

Circular References

fast-safe-stringify replaces circular references with the string "[Circular]".

// fast-safe-stringify
import stringify from 'fast-safe-stringify';

const obj = { a: 1 };
obj.self = obj;
console.log(stringify(obj));
// Output: "{\"a\":1,\"self\":\"[Circular]\"}"

json-stringify-safe also uses "[Circular]", but its implementation fails on complex graphs (e.g., multiple interlinked circular paths).

// json-stringify-safe
const stringifySafe = require('json-stringify-safe');

const obj = { a: 1 };
obj.self = obj;
console.log(stringifySafe(obj));
// Output: "{\"a\":1,\"self\":\"[Circular]\"}"

safe-json-stringify behaves similarly but uses a different internal tracking mechanism that can leak memory in long-running processes.

// safe-json-stringify
const safeStringify = require('safe-json-stringify');

const obj = { a: 1 };
obj.self = obj;
console.log(safeStringify(obj));
// Output: "{\"a\":1,\"self\":\"[Circular]\"}"

Functions, undefined, and Symbols

Native JSON.stringify() omits object properties with values of undefined, functions, or symbols. These libraries preserve structure by converting them to strings:

  • fast-safe-stringify: Converts undefined"undefined", function → "[Function: name]" or "[Function]", symbol → "[Symbol(name)]".
// fast-safe-stringify
const obj = {
  fn: () => {},
  undef: undefined,
  sym: Symbol('id')
};
console.log(stringify(obj));
// Output: "{\"fn\":\"[Function]\",\"undef\":\"undefined\",\"sym\":\"[Symbol(id)]\"}"
  • json-stringify-safe: Converts all three to "[object Object]" or similar generic placeholders, losing semantic meaning.
// json-stringify-safe
console.log(stringifySafe(obj));
// Output: "{\"fn\":\"[object Object]\",\"undef\":\"[object Object]\",\"sym\":\"[object Object]\"}"
  • safe-json-stringify: Similar to json-stringify-safe, uses vague placeholders like "[NonSerializable]".
// safe-json-stringify
console.log(safeStringify(obj));
// Output: "{\"fn\":\"[NonSerializable]\",\"undef\":\"[NonSerializable]\",\"sym\":\"[NonSerializable]\"}"

⚡ Performance: Speed Matters in Logging and Debugging

In high-throughput scenarios (e.g., logging middleware), serialization speed directly impacts application latency.

  • fast-safe-stringify is optimized in C-like JavaScript with minimal allocations. Benchmarks consistently show it’s 2–5x faster than the alternatives.
  • json-stringify-safe uses recursive traversal with heavy object copying, making it slow and memory-intensive.
  • safe-json-stringify falls in between but still lags significantly behind fast-safe-stringify.

Example in a logging context:

// Using fast-safe-stringify in a logger
app.use((req, res, next) => {
  console.log('Request:', stringify(req)); // Fast, safe
  next();
});

Switching to json-stringify-safe here would noticeably degrade request throughput under load.

🔧 API Design: Simplicity vs Flexibility

All three expose a single function matching JSON.stringify(value, replacer?, space?), but only fast-safe-stringify supports the full signature reliably.

  • fast-safe-stringify fully supports replacer and space arguments:
stringify({ a: 1, b: 2 }, ['a'], 2);
// Returns formatted string with only property 'a'
  • json-stringify-safe accepts extra arguments but ignores them or behaves unpredictably.
  • safe-json-stringify does not support replacer or space at all.

This makes fast-safe-stringify the only viable choice if you need pretty-printed output or custom property filtering.

🌐 Real-World Use Cases

✅ When You Need Reliable, High-Performance Serialization

Use fast-safe-stringify for:

  • Application logging (e.g., with Pino, Winston)
  • Debugging tools that inspect arbitrary objects
  • Serializing user-provided data that may contain cycles
  • Any production system where stability and speed are critical

⚠️ Legacy Code Only

Consider json-stringify-safe or safe-json-stringify only if:

  • You’re maintaining an old codebase already dependent on them
  • You cannot upgrade due to strict dependency policies
  • You’ve verified their behavior matches your exact use case (not recommended)

📊 Summary Table

Featurefast-safe-stringifyjson-stringify-safesafe-json-stringify
Maintenance Status✅ Actively maintained❌ Deprecated⚠️ Unmaintained
Circular Reference"[Circular]""[Circular]" (buggy)"[Circular]"
Functions/UndefinedDescriptive stringsGeneric placeholdersGeneric placeholders
Performance⚡ Very fast🐢 Slow🐢 Slow
Replacer/Space Support✅ Full support❌ Ignored/unreliable❌ Not supported
Memory Safety✅ No leaks⚠️ Possible leaks⚠️ Possible leaks

💡 Final Recommendation

For any new project requiring safe JSON serialization, fast-safe-stringify is the clear and only sensible choice. It’s fast, well-maintained, handles edge cases correctly, and integrates seamlessly into modern JavaScript toolchains. The other two packages exist only as historical artifacts — useful to recognize in legacy code, but never to adopt anew.

How to Choose: fast-safe-stringify vs json-stringify-safe vs safe-json-stringify

  • fast-safe-stringify:

    Choose fast-safe-stringify for any new project requiring safe JSON serialization. It's actively maintained, significantly faster than alternatives, correctly handles circular references and non-serializable values with descriptive placeholders, and fully supports the standard JSON.stringify() API including replacer and space arguments. It's the de facto standard in modern logging libraries like Pino.

  • json-stringify-safe:

    Avoid json-stringify-safe in new projects—it's officially deprecated and unmaintained since 2016. While it was once a common solution, it contains known bugs with complex circular structures, performs poorly, and provides vague placeholders for functions and undefined. Only consider it if you're maintaining legacy code that already depends on it.

  • safe-json-stringify:

    Do not use safe-json-stringify for new development. The package appears abandoned, with its GitHub repository archived and no updates since 2017. It lacks support for standard JSON.stringify() features like replacer and space, uses non-descriptive placeholders for problematic values, and may have memory leakage issues. Evaluate fast-safe-stringify instead.

README for fast-safe-stringify

fast-safe-stringify

Safe and fast serialization alternative to JSON.stringify.

Gracefully handles circular structures instead of throwing in most cases. It could return an error string if the circular object is too complex to analyze, e.g. in case there are proxies involved.

Provides a deterministic ("stable") version as well that will also gracefully handle circular structures. See the example below for further information.

Usage

The same as JSON.stringify.

stringify(value[, replacer[, space[, options]]])

const safeStringify = require('fast-safe-stringify')
const o = { a: 1 }
o.o = o

console.log(safeStringify(o))
// '{"a":1,"o":"[Circular]"}'
console.log(JSON.stringify(o))
// TypeError: Converting circular structure to JSON

function replacer(key, value) {
  console.log('Key:', JSON.stringify(key), 'Value:', JSON.stringify(value))
  // Remove the circular structure
  if (value === '[Circular]') {
    return
  }
  return value
}

// those are also defaults limits when no options object is passed into safeStringify
// configure it to lower the limit.
const options = {
  depthLimit: Number.MAX_SAFE_INTEGER,
  edgesLimit: Number.MAX_SAFE_INTEGER
};

const serialized = safeStringify(o, replacer, 2, options)
// Key: "" Value: {"a":1,"o":"[Circular]"}
// Key: "a" Value: 1
// Key: "o" Value: "[Circular]"
console.log(serialized)
// {
//  "a": 1
// }

Using the deterministic version also works the same:

const safeStringify = require('fast-safe-stringify')
const o = { b: 1, a: 0 }
o.o = o

console.log(safeStringify(o))
// '{"b":1,"a":0,"o":"[Circular]"}'
console.log(safeStringify.stableStringify(o))
// '{"a":0,"b":1,"o":"[Circular]"}'
console.log(JSON.stringify(o))
// TypeError: Converting circular structure to JSON

A faster and side-effect free implementation is available in the [safe-stable-stringify][] module. However it is still considered experimental due to a new and more complex implementation.

Replace strings constants

  • [Circular] - when same reference is found
  • [...] - when some limit from options object is reached

Differences to JSON.stringify

In general the behavior is identical to JSON.stringify. The replacer and space options are also available.

A few exceptions exist to JSON.stringify while using toJSON or replacer:

Regular safe stringify

  • Manipulating a circular structure of the passed in value in a toJSON or the replacer is not possible! It is possible for any other value and property.

  • In case a circular structure is detected and the replacer is used it will receive the string [Circular] as the argument instead of the circular object itself.

Deterministic ("stable") safe stringify

  • Manipulating the input object either in a toJSON or the replacer function will not have any effect on the output. The output entirely relies on the shape the input value had at the point passed to the stringify function!

  • In case a circular structure is detected and the replacer is used it will receive the string [Circular] as the argument instead of the circular object itself.

A side effect free variation without these limitations can be found as well (safe-stable-stringify). It is also faster than the current implementation. It is still considered experimental due to a new and more complex implementation.

Benchmarks

Although not JSON, the Node.js util.inspect method can be used for similar purposes (e.g. logging) and also handles circular references.

Here we compare fast-safe-stringify with some alternatives: (Lenovo T450s with a i7-5600U CPU using Node.js 8.9.4)

fast-safe-stringify:   simple object x 1,121,497 ops/sec ±0.75% (97 runs sampled)
fast-safe-stringify:   circular      x 560,126 ops/sec ±0.64% (96 runs sampled)
fast-safe-stringify:   deep          x 32,472 ops/sec ±0.57% (95 runs sampled)
fast-safe-stringify:   deep circular x 32,513 ops/sec ±0.80% (92 runs sampled)

util.inspect:          simple object x 272,837 ops/sec ±1.48% (90 runs sampled)
util.inspect:          circular      x 116,896 ops/sec ±1.19% (95 runs sampled)
util.inspect:          deep          x 19,382 ops/sec ±0.66% (92 runs sampled)
util.inspect:          deep circular x 18,717 ops/sec ±0.63% (96 runs sampled)

json-stringify-safe:   simple object x 233,621 ops/sec ±0.97% (94 runs sampled)
json-stringify-safe:   circular      x 110,409 ops/sec ±1.85% (95 runs sampled)
json-stringify-safe:   deep          x 8,705 ops/sec ±0.87% (96 runs sampled)
json-stringify-safe:   deep circular x 8,336 ops/sec ±2.20% (93 runs sampled)

For stable stringify comparisons, see the performance benchmarks in the safe-stable-stringify readme.

Protip

Whether fast-safe-stringify or alternatives are used: if the use case consists of deeply nested objects without circular references the following pattern will give best results. Shallow or one level nested objects on the other hand will slow down with it. It is entirely dependant on the use case.

const stringify = require('fast-safe-stringify')

function tryJSONStringify (obj) {
  try { return JSON.stringify(obj) } catch (_) {}
}

const serializedString = tryJSONStringify(deep) || stringify(deep)

Acknowledgements

Sponsored by nearForm

License

MIT