safe-stable-stringify

repository·main·Indexed 42 days ago

https://github.com/bridgear/safe-stable-stringify

A high-performance, deterministic serialization library and safe alternative to JSON.stringify() version 2.5.0. It ensures consistent output by sorting object keys and handles circular structures and BigInt values without throwing errors. Features include configurable circular reference placeholders, BigInt serialization options, and a strict mode for catching unsupported types.

Tokens
2.9K
Snippets
10
Records
11
Agent score
53%

What's inside safe-stable-stringify

  1. How safe-stable-stringify differs from JSON.stringify

    main

    While safe-stable-stringify is side-effect free and supports toJSON(), replacer, and space exactly like JSON.stringify(), there are four key differences:

    1. Circular values: Replaced with a configurable string (defaulting to [Circular]) instead of throwing.
    2. Object keys: Sorted by default to ensure deterministic output (configurable).
    3. BigInt: Serialized as a regular number (default) or string instead of throwing a TypeError.
    4. Boxed primitives: Values like Number(5) are treated as regular objects and are not unboxed.
  2. Use stringify(value[, replacer[, space]])

    main

    The stringify function provides a safe, deterministic, and fast alternative to JSON.stringify(). It handles circular structures and BigInt values gracefully instead of throwing errors. It supports the same replacer and space arguments as the standard JSON.stringify() API.

    Key advantages over JSON.stringify():

    • Circular structures: Replaced with a configurable string (default: '[Circular]').
    • BigInt: Converted to a number (default) or string (configurable) instead of throwing a TypeError.
    • Determinism: Object keys are sorted to ensure consistent output regardless of insertion order.
    const stringify = require('safe-stable-stringify')
    
    const bigint = { a: 0, c: 2n, b: 1 }
    stringify(bigint)
    // '{"a":0,"b":1,"c":2}'
    
    const circular = { b: 1, a: 0 }
    circular.circular = circular
    stringify(circular)
    // '{"a":0,"b":1,"circular":"[Circular]"}'
    
    stringify(circular, ['a', 'b'], 2)
    // {
    //   "a": 0,
    //   "b": 1
    // }
  3. Configure stringify behavior with stringify.configure(options)

    main

    Use stringify.configure(options) to create a new stringify function with custom serialization rules. This is useful for controlling how specific types (like BigInt or circular references) are handled, or for enforcing limits on depth and breadth.

    Returns a new function that accepts (value, replacer, space).

    import { configure } from 'safe-stable-stringify'
    
    const stringify = configure({
      bigint: true,
      circularValue: 'Magic circle!',
      deterministic: false,
      maximumDepth: 1,
      maximumBreadth: 4
    })
    
    const circular = {
      bigint: 999_999_999_999_999_999n,
      typed: new Uint8Array(3),
      deterministic: "I don't think so",
    }
    circular.circular = circular
    
    const stringified = stringify(circular, null, 4)
    console.log(stringified)
    // {
    //     "bigint": 999999999999999999,
    //     "typed": "[Object]",
    //     "deterministic": "I don't think so",
    //     "circular": "Magic circle!",
    //     "...": "2 items not stringified"
    // }
  4. Use strict mode to catch unsupported types

    main

    By default, safe-stable-stringify returns undefined for types it cannot serialize (like functions). If you want to ensure your data is fully serializable, enable strict: true in configure().

    When strict is enabled:

    1. bigint defaults to false.
    2. circularValue defaults to Error (causing it to throw on circularity).
    3. Any unsupported type encountered will throw an error describing the type.
    const stringify = configure({ strict: true });
    
    try {
      stringify({ fn: () => {} });
    } catch (e) {
      console.log(e.message); // "Object can not safely be stringified. Received type function"
    }
  5. Handle circular references with circularValue

    main

    To prevent infinite loops when encountering circular structures, use the circularValue option in configure().

    • String value: Use a custom string like '"[LOOP]"'.
    • null/undefined: Uses the standard behavior.
    • Error/TypeError: If you pass the Error or TypeError constructor, the stringifier will throw a TypeError when a circular reference is detected instead of returning a placeholder.
    • Default: If no option is provided, it uses '"[Circular]"'.
    const stringify = configure({ circularValue: '"[LOOP]"' });
    const obj = {};
    obj.self = obj;
    console.log(stringify(obj)); // '{"self":"[LOOP]"}'
  6. Configure BigInt handling

    main

    The bigint option controls how BigInt values are serialized.

    • If bigint: true (default): The value is converted to a raw number string (e.g., 100n becomes 100). Note that this produces invalid JSON if parsed by standard JSON.parse.
    • If bigint: 'string': The value is wrapped in double quotes (e.g., 100n becomes "100"). This produces valid JSON.
    • If bigint: false: BigInt values are treated as unsupported types (returning undefined or triggering strict mode).
    const stringify = configure({ bigint: 'string' });
    console.log(stringify({ val: 100n })); // '{"val":"100"}'
  7. Reference: stringify.configure options

    main

    The configure method accepts an options object to customize serialization behavior. Use these keys to control how the library handles edge cases and complex objects.

    // Configuration options reference:
    {
      bigint: true | 'string', // true: convert to number (default); 'string': serialize as JSON string; otherwise: ignore.
      circularValue: string | null | undefined | ErrorConstructor, // Value for circular refs. undefined: skip; Error: throw. Default: '[Circular]'.
      deterministic: boolean | function, // true (default) or a comparator function to guarantee key order.
      maximumBreadth: number, // Max entries per object. Output includes "..." count if exceeded. Default: Infinity.
      maximumDepth: number, // Max nesting levels. Objects at limit become '[Object]', arrays become '[Array]'. Default: Infinity.
      strict: boolean, // If true, throws on non-JSON values (functions, NaN, etc.). Default: false.
      safe: boolean // If true, .toJSON(), getters, and replacers that throw return the error message instead of throwing. Default: false.
    }
  8. Configure safe-stable-stringify settings with configure()

    main

    The configure function allows you to create a customized version of the stringifier by providing an options object. This is useful for setting global behaviors or creating specific instances with different serialization rules.

    import { configure } from 'safe-stable-stringify';
    
    const customStringify = configure({ /* options */ });
    const json = customStringify(obj);
  9. Use stringify() to create stable JSON strings

    main

    The stringify function provides a stable and safe alternative to JSON.stringify. It ensures deterministic output by sorting object keys and handles edge cases like circular references, BigInt values, and depth/breadth limits to prevent stack overflows or infinite loops.

    By default, stringify is deterministic (keys are sorted) and handles BigInt values. It also handles circular references by replacing them with a placeholder (defaulting to "[Circular]").

    const stringify = require('safe-stable-stringify');
    
    const obj = { b: 1, a: 2, c: { z: 10, y: 20 } };
    const result = stringify(obj);
    // result: '{"a":2,"b":1,"c":{"y":20,"z":10}}' (keys are sorted)
  10. Configure stringify behavior with configure()

    main

    You can create a customized version of the stringify function using configure(options). This allows you to set global behaviors for all subsequent calls made with the returned function.

    Common configuration options include:

    • deterministic: (boolean or function) Whether to sort object keys. Defaults to true.
    • bigint: (boolean or 'string') How to handle BigInt. If 'string', values are wrapped in quotes. Defaults to true.
    • circularValue: (string or null/undefined) The value used to replace circular references. Defaults to '"[Circular]"'. If set to Error or TypeError, it will throw an error instead.
    • strict: (boolean) If true, the stringifier will throw an error when encountering unsupported types (like functions) instead of returning undefined.
    • safe: (boolean) If true, calls to toJSON or the replacer function are wrapped in a try-catch to prevent errors from breaking the stringification process.
    • maximumDepth: (integer) Limits how deep the recursion goes. Objects exceeding this depth are replaced with '"[Object]"' or '"[Array]"'. Must be $\ge 1$.
    • maximumBreadth: (integer) Limits how many properties or array elements are stringified. Excess items are replaced with a summary message. Must be $\ge 1$.
    const { configure } = require('safe-stable-stringify');
    
    const customStringify = configure({
      deterministic: true,
      bigint: 'string',
      maximumDepth: 5,
      circularValue: '[[CIRCULAR]]'
    });
    
    const result = customStringify({ a: 1 });
  11. Use the stringify function via ESM

    main

    The default export and the stringify named export provide the core stringification functionality. This function is used to convert a JavaScript value into a JSON string with stable key ordering, ensuring that identical objects produce identical strings regardless of property insertion order.

    import stringify from 'safe-stable-stringify';
    
    const obj = { b: 1, a: 2 };
    const json = stringify(obj);
    // Result: '{"a":2,"b":1}' (keys are sorted)