Package Exports
- safe-json-value
Readme
JSON serialization should never fail.
Features
Prevent JSON.serialize()
from:
- Throwing
- Changing types, filtering or changing values unexpectedly
- Returning a very big output
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: 'cycle'
// }
// ]
Install
npm install safe-json-value
This package 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:
- Omitting properties which would throw,
change type unexpectedly or
be filtered with
JSON.stringify()
- Resolving properties which would change value with
JSON.stringify()
Applied recursively on object/array properties. This never throws.
Options
Object with the following properties.
maxSize
Type: number
Default: Number.POSITIVE_INFINITY
(no maximum size)
Maximum JSON.stringify(value).length
. Additional properties beyond the size
limit are omitted.
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.
It can be manipulated or
stringified
using wild-wild-parser
.
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: "bigint"
, "class"
,
"cycle"
, "function"
, "getter"
,
"infiniteNumber"
, "maxSize"
,
"notArrayIndex"
,
"notEnumerable"
,
"notConfigurable"
,
"notWritable"
, "symbolKey"
,
"symbolValue"
, "toJSON"
,
"uncaughtException"
, "undefined"
,
"unsafeGetter"
or
"unsafeToJSON"
.
changes[*].error
Type: Error?
Error that triggered the change. Only present if reason
is
"uncaughtException"
,
"unsafeGetter"
or
"unsafeToJSON"
.
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: { ...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}"
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) // '{}'
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 }
Big output
Big JSON strings can make a process, filesystem operation or network request crash.
When using the maxSize
option, properties that are too large are
omitted. Large values (including strings) are completely removed, not truncated.
const input = { one: true, two: 'a'.repeat(1e6) }
JSON.stringify(safeJsonValue(input, { maxSize: 1e5 }).value) // '{"one":true}"
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!