You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					178 lines
				
				6.4 KiB
			
		
		
			
		
	
	
					178 lines
				
				6.4 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								# safe-stable-stringify
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Safe, deterministic and fast serialization alternative to [JSON.stringify][].
							 | 
						||
| 
								 | 
							
								Zero dependencies. ESM and CJS. 100% coverage.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Gracefully handles circular structures and bigint instead of throwing.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Optional custom circular values, deterministic behavior or strict JSON
							 | 
						||
| 
								 | 
							
								compatibility check.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## stringify(value[, replacer[, space]])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The same as [JSON.stringify][].
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								* `value` {any}
							 | 
						||
| 
								 | 
							
								* `replacer` {string[]|function|null}
							 | 
						||
| 
								 | 
							
								* `space` {number|string}
							 | 
						||
| 
								 | 
							
								* Returns: {string}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```js
							 | 
						||
| 
								 | 
							
								const stringify = require('safe-stable-stringify')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const bigint = { a: 0, c: 2n, b: 1 }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								stringify(bigint)
							 | 
						||
| 
								 | 
							
								// '{"a":0,"b":1,"c":2}'
							 | 
						||
| 
								 | 
							
								JSON.stringify(bigint)
							 | 
						||
| 
								 | 
							
								// TypeError: Do not know how to serialize a BigInt
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const circular = { b: 1, a: 0 }
							 | 
						||
| 
								 | 
							
								circular.circular = circular
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								stringify(circular)
							 | 
						||
| 
								 | 
							
								// '{"a":0,"b":1,"circular":"[Circular]"}'
							 | 
						||
| 
								 | 
							
								JSON.stringify(circular)
							 | 
						||
| 
								 | 
							
								// TypeError: Converting circular structure to JSON
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								stringify(circular, ['a', 'b'], 2)
							 | 
						||
| 
								 | 
							
								// {
							 | 
						||
| 
								 | 
							
								//   "a": 0,
							 | 
						||
| 
								 | 
							
								//   "b": 1
							 | 
						||
| 
								 | 
							
								// }
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## stringify.configure(options)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								* `bigint` {boolean} If `true`, bigint values are converted to a number. Otherwise
							 | 
						||
| 
								 | 
							
								  they are ignored. **Default:** `true`.
							 | 
						||
| 
								 | 
							
								* `circularValue` {string|null|undefined|ErrorConstructor} Defines the value for
							 | 
						||
| 
								 | 
							
								  circular references. Set to `undefined`, circular properties are not
							 | 
						||
| 
								 | 
							
								  serialized (array entries are replaced with `null`). Set to `Error`, to throw
							 | 
						||
| 
								 | 
							
								  on circular references. **Default:** `'[Circular]'`.
							 | 
						||
| 
								 | 
							
								* `deterministic` {boolean} If `true`, guarantee a deterministic key order
							 | 
						||
| 
								 | 
							
								  instead of relying on the insertion order. **Default:** `true`.
							 | 
						||
| 
								 | 
							
								* `maximumBreadth` {number} Maximum number of entries to serialize per object
							 | 
						||
| 
								 | 
							
								  (at least one). The serialized output contains information about how many
							 | 
						||
| 
								 | 
							
								  entries have not been serialized. Ignored properties are counted as well
							 | 
						||
| 
								 | 
							
								  (e.g., properties with symbol values). Using the array replacer overrules this
							 | 
						||
| 
								 | 
							
								  option. **Default:** `Infinity`
							 | 
						||
| 
								 | 
							
								* `maximumDepth` {number} Maximum number of object nesting levels (at least 1)
							 | 
						||
| 
								 | 
							
								  that will be serialized. Objects at the maximum level are serialized as
							 | 
						||
| 
								 | 
							
								  `'[Object]'` and arrays as `'[Array]'`. **Default:** `Infinity`
							 | 
						||
| 
								 | 
							
								* `strict` {boolean} Instead of handling any JSON value gracefully, throw an
							 | 
						||
| 
								 | 
							
								  error in case it may not be represented as JSON (functions, NaN, ...).
							 | 
						||
| 
								 | 
							
								  Circular values and bigint values throw as well in case either option is not
							 | 
						||
| 
								 | 
							
								  explicitly defined. Sets and Maps are not detected! **Default:** `false`
							 | 
						||
| 
								 | 
							
								* Returns: {function} A stringify function with the options applied.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```js
							 | 
						||
| 
								 | 
							
								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
							 | 
						||
| 
								 | 
							
								circular.ignored = true
							 | 
						||
| 
								 | 
							
								circular.alsoIgnored = 'Yes!'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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"
							 | 
						||
| 
								 | 
							
								// }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const throwOnCircular = configure({
							 | 
						||
| 
								 | 
							
								  circularValue: Error
							 | 
						||
| 
								 | 
							
								})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								throwOnCircular(circular);
							 | 
						||
| 
								 | 
							
								// TypeError: Converting circular structure to JSON
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Differences to JSON.stringify
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								1. _Circular values_ are replaced with the string `[Circular]` (configurable).
							 | 
						||
| 
								 | 
							
								1. _Object keys_ are sorted instead of using the insertion order (configurable).
							 | 
						||
| 
								 | 
							
								1. _BigInt_ values are stringified as regular number instead of throwing a
							 | 
						||
| 
								 | 
							
								   TypeError (configurable).
							 | 
						||
| 
								 | 
							
								1. _Boxed primitives_ (e.g., `Number(5)`) are not unboxed and are handled as
							 | 
						||
| 
								 | 
							
								   regular object.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Those are the only differences to `JSON.stringify()`. This is a side effect free
							 | 
						||
| 
								 | 
							
								variant and [`toJSON`][], [`replacer`][] and the [`spacer`][] work the same as
							 | 
						||
| 
								 | 
							
								with `JSON.stringify()`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Performance / Benchmarks
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Currently this is by far the fastest known stable (deterministic) stringify
							 | 
						||
| 
								 | 
							
								implementation. This is especially important for big objects and TypedArrays.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								(Dell Precision 5540, i7-9850H CPU @ 2.60GHz, Node.js 16.11.1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```md
							 | 
						||
| 
								 | 
							
								simple:   simple object x 3,463,894 ops/sec ±0.44% (98 runs sampled)
							 | 
						||
| 
								 | 
							
								simple:   circular      x 1,236,007 ops/sec ±0.46% (99 runs sampled)
							 | 
						||
| 
								 | 
							
								simple:   deep          x 18,942 ops/sec ±0.41% (93 runs sampled)
							 | 
						||
| 
								 | 
							
								simple:   deep circular x 18,690 ops/sec ±0.72% (96 runs sampled)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								replacer:   simple object x 2,664,940 ops/sec ±0.31% (98 runs sampled)
							 | 
						||
| 
								 | 
							
								replacer:   circular      x 1,015,981 ops/sec ±0.09% (99 runs sampled)
							 | 
						||
| 
								 | 
							
								replacer:   deep          x 17,328 ops/sec ±0.38% (97 runs sampled)
							 | 
						||
| 
								 | 
							
								replacer:   deep circular x 17,071 ops/sec ±0.21% (98 runs sampled)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								array:   simple object x 3,869,608 ops/sec ±0.22% (98 runs sampled)
							 | 
						||
| 
								 | 
							
								array:   circular      x 3,853,943 ops/sec ±0.45% (96 runs sampled)
							 | 
						||
| 
								 | 
							
								array:   deep          x 3,563,227 ops/sec ±0.20% (100 runs sampled)
							 | 
						||
| 
								 | 
							
								array:   deep circular x 3,286,475 ops/sec ±0.07% (100 runs sampled)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								indentation:   simple object x 2,183,162 ops/sec ±0.66% (97 runs sampled)
							 | 
						||
| 
								 | 
							
								indentation:   circular      x 872,538 ops/sec ±0.57% (98 runs sampled)
							 | 
						||
| 
								 | 
							
								indentation:   deep          x 16,795 ops/sec ±0.48% (93 runs sampled)
							 | 
						||
| 
								 | 
							
								indentation:   deep circular x 16,443 ops/sec ±0.40% (97 runs sampled)
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Comparing `safe-stable-stringify` with known alternatives:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```md
							 | 
						||
| 
								 | 
							
								fast-json-stable-stringify x 18,765 ops/sec ±0.71% (94 runs sampled)
							 | 
						||
| 
								 | 
							
								json-stable-stringify x 13,870 ops/sec ±0.72% (94 runs sampled)
							 | 
						||
| 
								 | 
							
								fast-stable-stringify x 21,343 ops/sec ±0.33% (95 runs sampled)
							 | 
						||
| 
								 | 
							
								faster-stable-stringify x 17,707 ops/sec ±0.44% (97 runs sampled)
							 | 
						||
| 
								 | 
							
								json-stringify-deterministic x 11,208 ops/sec ±0.57% (98 runs sampled)
							 | 
						||
| 
								 | 
							
								fast-safe-stringify x 21,460 ops/sec ±0.75% (99 runs sampled)
							 | 
						||
| 
								 | 
							
								this x 30,367 ops/sec ±0.39% (96 runs sampled)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The fastest is this
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The `fast-safe-stringify` comparison uses the modules stable implementation.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Acknowledgements
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Sponsored by [MaibornWolff](https://www.maibornwolff.de/) and [nearForm](http://nearform.com)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## License
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								MIT
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								[`replacer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The%20replacer%20parameter
							 | 
						||
| 
								 | 
							
								[`spacer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The%20space%20argument
							 | 
						||
| 
								 | 
							
								[`toJSON`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON()_behavior
							 | 
						||
| 
								 | 
							
								[JSON.stringify]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
							 |