Popularity
2.5
Stable
Activity
7.1
-
206
4
6

Description

Prevent `JSON.serialize()` from throwing, changing types, filtering or transforming values unexpectedly.

Programming language: JavaScript
License: Apache License 2.0
Tags: JSON     Nodejs     Parsing     Node     JavaScript     Parser     serialization    
Latest version: v1.10.0

safe-json-value alternatives and similar modules

Based on the "Parsing" category.
Alternatively, view safe-json-value alternatives based on common mentions on social networks and blogs.

Do you think we are missing an alternative of safe-json-value or a related project?

Add another 'Parsing' Module

README

Node Browsers TypeScript Codecov Minified size Twitter Medium

⛑️ JSON serialization should never fail.

Features

Prevent JSON.stringify() from:

Example

<!-- eslint-disable fp/no-mutation -->

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

<!-- eslint-disable fp/no-mutation -->

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

<!-- eslint-disable fp/no-get-set -->

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

<!-- eslint-disable fp/no-proxy -->

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

<!-- eslint-disable fp/no-mutating-methods, fp/no-mutation -->

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

<!-- eslint-disable fp/no-mutating-methods, fp/no-mutation -->

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

<!-- eslint-disable symbol-description -->

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

<!-- eslint-disable no-unused-expressions -->

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

undefined

<!-- eslint-disable no-unused-expressions -->

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

Symbol values

<!-- eslint-disable no-unused-expressions, symbol-description -->

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

Symbol keys

<!-- eslint-disable no-unused-expressions, symbol-description -->

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

Non-enumerable keys

<!-- eslint-disable no-unused-expressions, fp/no-mutating-methods -->

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

<!-- eslint-disable no-unused-expressions, fp/no-mutation -->

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()

<!-- eslint-disable no-unused-expressions -->

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

Dates

<!-- eslint-disable no-unused-expressions -->

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

<!-- eslint-disable no-unused-expressions -->

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

Getters

<!-- eslint-disable no-unused-expressions, fp/no-get-set -->

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

Proxies

<!-- eslint-disable no-unused-expressions, fp/no-proxy -->

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](../../issues).

Everyone is welcome regardless of personal background. We enforce a [Code of conduct](CODE_OF_CONDUCT.md) 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](CONTRIBUTING.md). Pull requests are welcome!

<!-- Thanks go to our wonderful contributors: -->

<!-- ALL-CONTRIBUTORS-LIST:START --> <!-- prettier-ignore-start --> <!-- markdownlint-disable --> ehmicky💻 🎨 🤔 📖 Pedro Augusto de Paula Barbosa📖

<!-- markdownlint-restore --> <!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->