Skip to content

Instantly share code, notes, and snippets.

@mrtnzlml
Created May 27, 2019 18:17
Show Gist options
  • Save mrtnzlml/e9d75fc6390cea5695d7abbc8c0be167 to your computer and use it in GitHub Desktop.
Save mrtnzlml/e9d75fc6390cea5695d7abbc8c0be167 to your computer and use it in GitHub Desktop.
// @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