⛑️ JSON serialization should never fail.
Prevent JSON.stringify() from:
- Throwing
- Changing types
- Filtering or transforming values unexpectedly
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' // } // ]npm install safe-json-valueThis package works in both Node.js >=18.18.0 and browsers.
This is an ES module. It must be loaded using an import or import() statement, not require(). If TypeScript is used, it must be configured to output ES modules, not CommonJS.
value any
options Options?
Return value: object
Makes value JSON-safe by:
- Omitting properties which would throw, change type unexpectedly or be filtered with
JSON.stringify() - Resolving properties which would change value with
JSON.stringify()
This never throws.
Object with the following properties.
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}"Type: boolean
Default: false
If false, object/array properties are processed recursively. Please note that cycles are not removed when this is true.
Object with the following properties.
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.
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.
Type: Array<string | symbol | number>
Property path.
Type: any
Property value before the change.
Type: any
Property value after the change. undefined means the property was omitted.
Type: string
Reason for the change among:
- Exceptions:
"unsafeCycle","unsafeBigInt","unsafeSize","unsafeException","unsafeToJSON","unsafeGetter" - Invalid descriptors:
"descriptorNotWritable","descriptorNotConfigurable" - Unexpected types:
"unstableInfinite" - Filtered values:
"ignoredFunction","ignoredUndefined","ignoredSymbolValue","ignoredSymbolKey","ignoredNotEnumerable","ignoredArrayProperty" - Unresolved values:
"unresolvedToJSON","unresolvedClass","unresolvedGetter"
Type: Error?
Error that triggered the change. Only present if reason is "unsafeException", "unsafeToJSON" or "unsafeGetter".
This is a list of all possible changes applied to make the value JSON-safe.
JSON.stringify() can throw on specific properties. Those are omitted.
const input = { one: true } input.self = input JSON.stringify(input) // Throws due to cycle JSON.stringify(safeJsonValue(input).value) // '{"one":true}"const input = { toJSON: () => ({ one: true, input }) } JSON.stringify(input) // Throws due to infinite `toJSON()` recursion JSON.stringify(safeJsonValue(input).value) // '{"one":true,"input":{...}}"const input = { one: true, two: 0n } JSON.stringify(input) // Throws due to BigInt JSON.stringify(safeJsonValue(input).value) // '{"one":true}"const input = { one: true, two: '\n'.repeat(5e8) } JSON.stringify(input) // Throws due to max string length JSON.stringify(safeJsonValue(input).value) // '{"one":true}"const input = { one: true, two: { toJSON: () => { throw new Error('example') }, }, } JSON.stringify(input) // Throws due to `toJSON()` JSON.stringify(safeJsonValue(input).value) // '{"one":true}"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}"const input = new Proxy( { one: false }, { get: () => { throw new Error('example') }, }, ) JSON.stringify(input) // Throws due to proxy JSON.stringify(safeJsonValue(input).value) // '{}'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 writableconst 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 })JSON.stringify() changes the types of specific values unexpectedly. Those are omitted.
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}"const input = [true, undefined, Symbol(), false] JSON.stringify(input) // '[true, null, null, false]' JSON.stringify(safeJsonValue(input).value) // '[true, false]'JSON.stringify() omits some specific types. Those are omitted right away to prevent any unexpected output.
const input = { one: true, two: () => {} } JSON.parse(JSON.stringify(input)) // { one: true } safeJsonValue(input).value // { one: true }const input = { one: true, two: undefined } JSON.parse(JSON.stringify(input)) // { one: true } safeJsonValue(input).value // { one: true }const input = { one: true, two: Symbol() } JSON.parse(JSON.stringify(input)) // { one: true } safeJsonValue(input).value // { one: true }const input = { one: true, [Symbol()]: true } JSON.parse(JSON.stringify(input)) // { one: true } safeJsonValue(input).value // { one: true }const input = { one: true } Object.defineProperty(input, 'two', { value: true, enumerable: false }) JSON.parse(JSON.stringify(input)) // { one: true } safeJsonValue(input).value // { one: true }const input = [true] input.prop = true JSON.parse(JSON.stringify(input)) // [true] safeJsonValue(input).value // [true]JSON.stringify() can transform some values. Those are resolved right away to prevent any unexpected output.
const input = { toJSON: () => ({ one: true }), } JSON.parse(JSON.stringify(input)) // { one: true } safeJsonValue(input).value // { one: true }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' }const input = { one: new Set([]) } JSON.parse(JSON.stringify(input)) // { one: {} } safeJsonValue(input).value // { one: {} }const input = { get one() { return true }, } JSON.parse(JSON.stringify(input)) // { one: true } safeJsonValue(input).value // { one: true }const input = new Proxy( { one: false }, { get: () => true, }, ) JSON.parse(JSON.stringify(input)) // { one: true } safeJsonValue(input).value // { one: true }is-json-value: Check if a value is valid JSONtruncate-json: Truncate a JSON stringguess-json-indent: Guess the indentation of a JSON stringerror-serializer: Convert errors to/from plain objects
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.
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 📖 |