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.
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.
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-safeandsafe-json-stringifyin new projects. Only consider them for legacy compatibility.
All three libraries attempt to avoid throwing errors when encountering circular references or non-serializable types, but their strategies differ.
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]\"}"
undefined, and SymbolsNative 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]\"}"
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.
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.
Use fast-safe-stringify for:
Consider json-stringify-safe or safe-json-stringify only if:
| Feature | fast-safe-stringify | json-stringify-safe | safe-json-stringify |
|---|---|---|---|
| Maintenance Status | ✅ Actively maintained | ❌ Deprecated | ⚠️ Unmaintained |
| Circular Reference | "[Circular]" | "[Circular]" (buggy) | "[Circular]" |
| Functions/Undefined | Descriptive strings | Generic placeholders | Generic placeholders |
| Performance | ⚡ Very fast | 🐢 Slow | 🐢 Slow |
| Replacer/Space Support | ✅ Full support | ❌ Ignored/unreliable | ❌ Not supported |
| Memory Safety | ✅ No leaks | ⚠️ Possible leaks | ⚠️ Possible leaks |
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.
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.
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.
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.
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.
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.
[Circular] - when same reference is found[...] - when some limit from options object is reachedIn 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:
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.
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.
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.
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)
Sponsored by nearForm
MIT