Neolithic Programming Machine

    safe-json-value
    TypeScript icon, indicating that this package has built-in type declarations

    1.11.0 • Public • Published
    modern-errors logo

    Node Browsers TypeScript Codecov Minified size Mastodon Medium

    ⛑️ JSON serialization should never fail.

    Features

    Prevent JSON.stringify() from:

    Example

    import safeJsonValue from 'safe-json-value'
    
    const input = { one: true }
    input.self = input
    
    JSON.stringify(input) // Throws due to cycle
    const { value, changes } = safeJsonValue(input)
    JSON.stringify(value) // '{"one":true}"
    
    console.log(changes) // List of changed properties
    // [
    //   {
    //     path: ['self'],
    //     oldValue: <ref *1> { one: true, self: [Circular *1] },
    //     newValue: undefined,
    //     reason: 'unsafeCycle'
    //   }
    // ]

    Install

    npm install safe-json-value

    This package works in both Node.js >=14.18.0 and browsers. It is an ES module and must be loaded using an import or import() statement, not require().

    API

    safeJsonValue(value, options?)

    value any
    options Options?
    Return value: object

    Makes value JSON-safe by:

    This never throws.

    Options

    Object with the following properties.

    maxSize

    Type: number
    Default: 1e7

    Big JSON strings can make a process, filesystem operation or network request crash. maxSize prevents it by setting a maximum JSON.stringify(value).length.

    Additional properties beyond the size limit are omitted. They are completely removed, not truncated (including strings).

    const input = { one: true, two: 'a'.repeat(1e6) }
    JSON.stringify(safeJsonValue(input, { maxSize: 1e5 }).value) // '{"one":true}"

    shallow

    Type: boolean
    Default: false

    If false, object/array properties are processed recursively. Please note that cycles are not removed when this is true.

    Return value

    Object with the following properties.

    value

    Type: any

    Copy of the input value after applying all the changes to make it JSON-safe.

    The top-level value itself might be changed (including to undefined) if it is either invalid JSON or has a toJSON() method.

    The value is not serialized to a JSON string. This allows choosing the serialization format (JSON, YAML, etc.), processing the value, etc.

    changes

    Type: Change[]

    List of changes applied to value. Each item is an individual change to a specific property. A given property might have multiple changes, listed in order.

    changes[*].path

    Type: Array<string | symbol | number>

    Property path.

    changes[*].oldValue

    Type: any

    Property value before the change.

    changes[*].newValue

    Type: any

    Property value after the change. undefined means the property was omitted.

    changes[*].reason

    Type: string

    Reason for the change among:

    changes[*].error

    Type: Error?

    Error that triggered the change. Only present if reason is "unsafeException", "unsafeToJSON" or "unsafeGetter".

    Changes

    This is a list of all possible changes applied to make the value JSON-safe.

    Exceptions

    JSON.stringify() can throw on specific properties. Those are omitted.

    Cycles

    const input = { one: true }
    input.self = input
    JSON.stringify(input) // Throws due to cycle
    JSON.stringify(safeJsonValue(input).value) // '{"one":true}"

    Infinite recursion

    const input = { toJSON: () => ({ one: true, input }) }
    JSON.stringify(input) // Throws due to infinite `toJSON()` recursion
    JSON.stringify(safeJsonValue(input).value) // '{"one":true,"input":{...}}"

    BigInt

    const input = { one: true, two: 0n }
    JSON.stringify(input) // Throws due to BigInt
    JSON.stringify(safeJsonValue(input).value) // '{"one":true}"

    Big output

    const input = { one: true, two: '\n'.repeat(5e8) }
    JSON.stringify(input) // Throws due to max string length
    JSON.stringify(safeJsonValue(input).value) // '{"one":true}"

    Exceptions in toJSON()

    const input = {
      one: true,
      two: {
        toJSON() {
          throw new Error('example')
        },
      },
    }
    JSON.stringify(input) // Throws due to `toJSON()`
    JSON.stringify(safeJsonValue(input).value) // '{"one":true}"

    Exceptions in getters

    const input = {
      one: true,
      get two() {
        throw new Error('example')
      },
    }
    JSON.stringify(input) // Throws due to `get two()`
    JSON.stringify(safeJsonValue(input).value) // '{"one":true}"

    Exceptions in proxies

    const input = new Proxy(
      { one: false },
      {
        get() {
          throw new Error('example')
        },
      },
    )
    JSON.stringify(input) // Throws due to proxy
    JSON.stringify(safeJsonValue(input).value) // '{}'

    Invalid descriptors

    Non-writable properties

    const input = {}
    Object.defineProperty(input, 'one', {
      value: true,
      enumerable: true,
      writable: false,
      configurable: true,
    })
    input.one = false // Throws: non-writable
    const safeInput = safeJsonValue(input).value
    safeInput.one = false // Does not throw: now writable

    Non-configurable properties

    const input = {}
    Object.defineProperty(input, 'one', {
      value: true,
      enumerable: true,
      writable: true,
      configurable: false,
    })
    // Throws: non-configurable
    Object.defineProperty(input, 'one', { value: false, enumerable: false })
    const safeInput = safeJsonValue(input).value
    // Does not throw: now configurable
    Object.defineProperty(safeInput, 'one', { value: false, enumerable: false })

    Unexpected types

    JSON.stringify() changes the types of specific values unexpectedly. Those are omitted.

    NaN and Infinity

    const input = { one: true, two: Number.NaN, three: Number.POSITIVE_INFINITY }
    JSON.stringify(input) // '{"one":true,"two":null,"three":null}"
    JSON.stringify(safeJsonValue(input).value) // '{"one":true}"

    Invalid array items

    const input = [true, undefined, Symbol(), false]
    JSON.stringify(input) // '[true, null, null, false]'
    JSON.stringify(safeJsonValue(input).value) // '[true, false]'

    Filtered values

    JSON.stringify() omits some specific types. Those are omitted right away to prevent any unexpected output.

    Functions

    const input = { one: true, two() {} }
    JSON.parse(JSON.stringify(input)) // { one: true }
    safeJsonValue(input).value // { one: true }

    undefined

    const input = { one: true, two: undefined }
    JSON.parse(JSON.stringify(input)) // { one: true }
    safeJsonValue(input).value // { one: true }

    Symbol values

    const input = { one: true, two: Symbol() }
    JSON.parse(JSON.stringify(input)) // { one: true }
    safeJsonValue(input).value // { one: true }

    Symbol keys

    const input = { one: true, [Symbol()]: true }
    JSON.parse(JSON.stringify(input)) // { one: true }
    safeJsonValue(input).value // { one: true }

    Non-enumerable keys

    const input = { one: true }
    Object.defineProperty(input, 'two', { value: true, enumerable: false })
    JSON.parse(JSON.stringify(input)) // { one: true }
    safeJsonValue(input).value // { one: true }

    Array properties

    const input = [true]
    input.prop = true
    JSON.parse(JSON.stringify(input)) // [true]
    safeJsonValue(input).value // [true]

    Unresolved values

    JSON.stringify() can transform some values. Those are resolved right away to prevent any unexpected output.

    toJSON()

    const input = {
      toJSON() {
        return { one: true }
      },
    }
    JSON.parse(JSON.stringify(input)) // { one: true }
    safeJsonValue(input).value // { one: true }

    Dates

    const input = { one: new Date() }
    JSON.parse(JSON.stringify(input)) // { one: '2022-07-29T14:37:40.865Z' }
    safeJsonValue(input).value // { one: '2022-07-29T14:37:40.865Z' }

    Classes

    const input = { one: new Set([]) }
    JSON.parse(JSON.stringify(input)) // { one: {} }
    safeJsonValue(input).value // { one: {} }

    Getters

    const input = {
      get one() {
        return true
      },
    }
    JSON.parse(JSON.stringify(input)) // { one: true }
    safeJsonValue(input).value // { one: true }

    Proxies

    const input = new Proxy(
      { one: false },
      {
        get() {
          return true
        },
      },
    )
    JSON.parse(JSON.stringify(input)) // { one: true }
    safeJsonValue(input).value // { one: true }

    Related projects

    Support

    For any question, don't hesitate to submit an issue on GitHub.

    Everyone is welcome regardless of personal background. We enforce a Code of conduct in order to promote a positive and inclusive environment.

    Contributing

    This project was made with ❤️. The simplest way to give back is by starring and sharing it online.

    If the documentation is unclear or has a typo, please click on the page's Edit button (pencil icon) and suggest a correction.

    If you would like to help us fix a bug or add a new feature, please check our guidelines. Pull requests are welcome!


    ehmicky

    💻 🎨 🤔 📖

    Pedro Augusto de Paula Barbosa

    📖

    Install

    npm i safe-json-value

    DownloadsWeekly Downloads

    234

    Version

    1.11.0

    License

    Apache-2.0

    Unpacked Size

    41.5 kB

    Total Files

    17

    Last publish

    Collaborators

    • ehmicky