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.
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.
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.
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.
Like JSON.stringify, but doesn't throw on circular references.
Takes the same arguments as JSON.stringify.
var stringify = require('json-stringify-safe');
var circularObj = {};
circularObj.circularRef = circularObj;
circularObj.list = [ circularObj, circularObj ];
console.log(stringify(circularObj, null, 2));
Output:
{
"circularRef": "[Circular]",
"list": [
"[Circular]",
"[Circular]"
]
}
stringify(obj, serializer, indent, decycler)
The first three arguments are the same as to JSON.stringify. The last is an argument that's only used when the object has been seen already.
The default decycler function returns the string '[Circular]'.
If, for example, you pass in function(k,v){} (return nothing) then it
will prune cycles. If you pass in function(k,v){ return {foo: 'bar'}},
then cyclical objects will always be represented as {"foo":"bar"} in
the result.
stringify.getSerialize(serializer, decycler)
Returns a serializer that can be used elsewhere. This is the actual function that's passed to JSON.stringify.
Note that the function returned from getSerialize is stateful for now, so
do not use it more than once.