Deterministic and safely JSON.stringify to quickly serialize JavaScript objects
npx @tessl/cli install tessl/npm-safe-stable-stringify@2.5.0Safe Stable Stringify is a deterministic and fast serialization alternative to JSON.stringify with zero dependencies. It gracefully handles circular structures and bigint values instead of throwing errors, offers optional custom circular values and deterministic behavior, and maintains 100% test coverage.
npm install safe-stable-stringifyimport stringify, { configure } from "safe-stable-stringify";For CommonJS:
const stringify = require("safe-stable-stringify");
const { configure } = require("safe-stable-stringify");For named imports (ESM):
import { configure, stringify } from "safe-stable-stringify";import stringify from "safe-stable-stringify";
// Handle bigint values (JSON.stringify would throw)
const bigintObj = { a: 0, c: 2n, b: 1 };
console.log(stringify(bigintObj));
// Output: '{"a":0,"b":1,"c":2}'
// Handle circular references (JSON.stringify would throw)
const circular = { name: "John", age: 30 };
circular.self = circular;
console.log(stringify(circular));
// Output: '{"age":30,"name":"John","self":"[Circular]"}'
// Use with replacer and space parameters (same as JSON.stringify)
console.log(stringify(circular, ['name', 'age'], 2));
// Output:
// {
// "name": "John",
// "age": 30
// }Main function that safely serializes JavaScript objects to JSON strings with deterministic behavior.
/**
* Safely stringify JavaScript objects to JSON with circular reference and bigint handling
* @param value - The value to stringify
* @param replacer - Optional replacer function or array (same as JSON.stringify)
* @param space - Optional space parameter for formatting (same as JSON.stringify)
* @returns JSON string representation or undefined for unstringifiable values
*/
function stringify(value: undefined | symbol | ((...args: unknown[]) => unknown), replacer?: Replacer, space?: string | number): undefined;
function stringify(value: string | number | unknown[] | null | boolean | object, replacer?: Replacer, space?: string | number): string;
function stringify(value: unknown, replacer?: ((key: string, value: unknown) => unknown) | (number | string)[] | null | undefined, space?: string | number): string | undefined;
type Replacer = (number | string)[] | null | undefined | ((key: string, value: unknown) => string | number | boolean | null | object);Creates a customized stringify function with specific options for handling bigint, circular references, deterministic behavior, and other serialization settings.
/**
* Creates a customized stringify function with specified options
* @param options - Configuration options object
* @returns Configured stringify function with applied options
*/
function configure(options: StringifyOptions): typeof stringify;
interface StringifyOptions {
/** Convert bigint values to numeric strings instead of ignoring them (default: true) */
bigint?: boolean;
/**
* Value to use for circular references
* - string: custom replacement text
* - null/undefined: omit circular properties
* - Error/TypeError: throw on circular references
* (default: '[Circular]')
*/
circularValue?: string | null | undefined | ErrorConstructor | TypeErrorConstructor;
/**
* Ensure deterministic key ordering
* - true: sort keys alphabetically
* - false: preserve insertion order
* - function: custom comparator for sorting
* (default: true)
*/
deterministic?: boolean | ((a: string, b: string) => number);
/** Maximum number of properties to serialize per object (default: Infinity) */
maximumBreadth?: number;
/** Maximum object nesting levels to serialize (default: Infinity) */
maximumDepth?: number;
/** Throw errors for non-JSON values instead of graceful handling (default: false) */
strict?: boolean;
}Usage Examples:
import { configure } from "safe-stable-stringify";
// Custom configuration
const customStringify = configure({
bigint: true, // Convert bigint to numeric JSON representation
circularValue: "CIRCULAR_REF", // Custom circular reference text
deterministic: false, // Preserve insertion order
maximumDepth: 3, // Limit nesting depth
maximumBreadth: 10 // Limit properties per object
});
const data = {
bigNum: 999_999_999_999_999_999n,
deep: { level1: { level2: { level3: { level4: "too deep" } } } },
manyProps: Object.fromEntries(Array.from({length: 15}, (_, i) => [`prop${i}`, i]))
};
data.circular = data;
console.log(customStringify(data, null, 2));
// Bigint converted to numeric JSON, circular ref replaced, deep object truncated
// Strict mode (throws on problematic values)
const strictStringify = configure({
strict: true,
bigint: false, // Must be false in strict mode unless explicitly true
circularValue: Error // Throw on circular references
});
try {
strictStringify({ fn: () => {} }); // Throws error for function
} catch (error) {
console.log("Strict mode prevented serialization");
}The default export function has additional properties attached for convenience:
/**
* Reference to the main stringify function (same as default export)
*/
stringify.stringify: typeof stringify;
/**
* Default export reference for compatibility
*/
stringify.default: typeof stringify;
/**
* Configuration function attached to the main function
*/
stringify.configure: typeof configure;These properties provide multiple ways to access the functionality:
import stringify from "safe-stable-stringify";
// All of these are equivalent
stringify({ test: true });
stringify.stringify({ test: true });
stringify.default({ test: true });
// Configure can be accessed directly from the main function
const customStringify = stringify.configure({ bigint: false });'[Circular]') instead of throwingNumber(5) stays as object)The package includes complete TypeScript definitions with:
// TypeScript automatically infers return types
const result1: string = stringify({ name: "John" }); // Always string for objects
const result2: undefined = stringify(Symbol("test")); // Undefined for symbols
const result3: string | undefined = stringify(someUnknown); // Union type for unknown inputs