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.
84 lines
4.3 KiB
84 lines
4.3 KiB
import { AutoCleanedStrongCache, cacheSizes, } from "../../utilities/caching/index.js"; |
|
import { registerGlobalCache } from "../caching/getMemoryInternals.js"; |
|
/** |
|
* Like JSON.stringify, but with object keys always sorted in the same order. |
|
* |
|
* To achieve performant sorting, this function uses a Map from JSON-serialized |
|
* arrays of keys (in any order) to sorted arrays of the same keys, with a |
|
* single sorted array reference shared by all permutations of the keys. |
|
* |
|
* As a drawback, this function will add a little bit more memory for every |
|
* object encountered that has different (more, less, a different order of) keys |
|
* than in the past. |
|
* |
|
* In a typical application, this extra memory usage should not play a |
|
* significant role, as `canonicalStringify` will be called for only a limited |
|
* number of object shapes, and the cache will not grow beyond a certain point. |
|
* But in some edge cases, this could be a problem, so we provide |
|
* canonicalStringify.reset() as a way of clearing the cache. |
|
* */ |
|
export var canonicalStringify = Object.assign(function canonicalStringify(value) { |
|
return JSON.stringify(value, stableObjectReplacer); |
|
}, { |
|
reset: function () { |
|
// Clearing the sortingMap will reclaim all cached memory, without |
|
// affecting the logical results of canonicalStringify, but potentially |
|
// sacrificing performance until the cache is refilled. |
|
sortingMap = new AutoCleanedStrongCache(cacheSizes.canonicalStringify || 1000 /* defaultCacheSizes.canonicalStringify */); |
|
}, |
|
}); |
|
if (globalThis.__DEV__ !== false) { |
|
registerGlobalCache("canonicalStringify", function () { return sortingMap.size; }); |
|
} |
|
// Values are JSON-serialized arrays of object keys (in any order), and values |
|
// are sorted arrays of the same keys. |
|
var sortingMap; |
|
canonicalStringify.reset(); |
|
// The JSON.stringify function takes an optional second argument called a |
|
// replacer function. This function is called for each key-value pair in the |
|
// object being stringified, and its return value is used instead of the |
|
// original value. If the replacer function returns a new value, that value is |
|
// stringified as JSON instead of the original value of the property. |
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter |
|
function stableObjectReplacer(key, value) { |
|
if (value && typeof value === "object") { |
|
var proto = Object.getPrototypeOf(value); |
|
// We don't want to mess with objects that are not "plain" objects, which |
|
// means their prototype is either Object.prototype or null. This check also |
|
// prevents needlessly rearranging the indices of arrays. |
|
if (proto === Object.prototype || proto === null) { |
|
var keys = Object.keys(value); |
|
// If keys is already sorted, let JSON.stringify serialize the original |
|
// value instead of creating a new object with keys in the same order. |
|
if (keys.every(everyKeyInOrder)) |
|
return value; |
|
var unsortedKey = JSON.stringify(keys); |
|
var sortedKeys = sortingMap.get(unsortedKey); |
|
if (!sortedKeys) { |
|
keys.sort(); |
|
var sortedKey = JSON.stringify(keys); |
|
// Checking for sortedKey in the sortingMap allows us to share the same |
|
// sorted array reference for all permutations of the same set of keys. |
|
sortedKeys = sortingMap.get(sortedKey) || keys; |
|
sortingMap.set(unsortedKey, sortedKeys); |
|
sortingMap.set(sortedKey, sortedKeys); |
|
} |
|
var sortedObject_1 = Object.create(proto); |
|
// Reassigning the keys in sorted order will cause JSON.stringify to |
|
// serialize them in sorted order. |
|
sortedKeys.forEach(function (key) { |
|
sortedObject_1[key] = value[key]; |
|
}); |
|
return sortedObject_1; |
|
} |
|
} |
|
return value; |
|
} |
|
// Since everything that happens in stableObjectReplacer benefits from being as |
|
// efficient as possible, we use a static function as the callback for |
|
// keys.every in order to test if the provided keys are already sorted without |
|
// allocating extra memory for a callback. |
|
function everyKeyInOrder(key, i, keys) { |
|
return i === 0 || keys[i - 1] <= key; |
|
} |
|
//# sourceMappingURL=canonicalStringify.js.map
|