Created
May 27, 2019 18:17
-
-
Save mrtnzlml/e9d75fc6390cea5695d7abbc8c0be167 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @flow strict | |
import isObject from './isObject'; | |
// Inspiration (modified and fixed stability): | |
// https://github.com/facebook/relay/blob/87930e12ed9694865d3a70dda564c0711e2890fd/packages/relay-runtime/util/stableCopy.js | |
/** | |
* @see http://www.ecma-international.org/ecma-262/6.0/#sec-sortcompare | |
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort | |
*/ | |
function defaultCompare(x, y): 0 | 1 | -1 { | |
if (x === undefined && y === undefined) { | |
return 0; | |
} else if (x === undefined) { | |
return 1; | |
} else if (y === undefined) { | |
return -1; | |
} | |
const xString = toString(x); | |
const yString = toString(y); | |
if (xString < yString) { | |
return -1; | |
} else if (xString > yString) { | |
return 1; | |
} | |
return 0; | |
} | |
/** | |
* @see http://www.ecma-international.org/ecma-262/6.0/#sec-tostring | |
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString | |
*/ | |
function toString(obj): string { | |
if (obj === null) { | |
return 'null'; | |
} else if (typeof obj === 'boolean' || typeof obj === 'number') { | |
return obj.toString(); | |
} else if (typeof obj === 'string') { | |
return obj; | |
} else if (typeof obj === 'symbol') { | |
throw new TypeError(); | |
} | |
// only Array and Object should be left | |
return Object.entries(obj) | |
.sort() | |
.toString(); | |
} | |
/** | |
* Creates a copy of the provided value, ensuring any nested objects have their | |
* keys sorted such that equivalent values would have identical JSON.stringify | |
* results. | |
* | |
* Please note! This function sorts arrays as well which is the biggest | |
* difference compared to any other solution. This means that it is truly stable | |
* however it can be only used for stable stringification since order of elements | |
* in arrays is otherwise significant! | |
*/ | |
function stableSort(value: mixed) { | |
if (Array.isArray(value)) { | |
return value.map(stableSort).sort(defaultCompare); | |
} | |
if (!isObject(value)) { | |
return value; | |
} | |
const keys = Object.keys(value).sort(); | |
const stable = {}; | |
for (let i = 0; i < keys.length; i++) { | |
stable[keys[i]] = stableSort(value[keys[i]]); | |
} | |
return stable; | |
} | |
export default function stableStringify( | |
value: mixed, | |
replacer?: ?Array<string>, | |
space?: number, | |
): string { | |
return JSON.stringify(stableSort(value), replacer, space) ?? 'undefined'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment