From 842eb5e2b820f5d0aec4746c229acaeb69306d1b Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Fri, 14 May 2021 17:21:52 -0400 Subject: [PATCH] Use ObjectCanon to speed up stable stringification. This reimplementation of the stable `stringify` function used by `getStoreKeyName` builds on the `ObjectCanon` introduced by my PR #7439, which guarantees canonical objects keys are always in sorted order. --- src/cache/inmemory/object-canon.ts | 27 ++++++++++++++++++++++++--- src/cache/inmemory/policies.ts | 6 ++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/cache/inmemory/object-canon.ts b/src/cache/inmemory/object-canon.ts index 0be454bc0d7..84c7c008cd6 100644 --- a/src/cache/inmemory/object-canon.ts +++ b/src/cache/inmemory/object-canon.ts @@ -2,7 +2,7 @@ import { Trie } from "@wry/trie"; import { canUseWeakMap } from "../../utilities"; import { objToStr } from "./helpers"; -function isObjectOrArray(value: any): boolean { +function isObjectOrArray(value: any): value is object { return !!value && typeof value === "object"; } @@ -109,7 +109,7 @@ export class ObjectCanon { switch (objToStr.call(value)) { case "[object Array]": { if (this.known.has(value)) return value; - const array: any[] = value.map(this.admit, this); + const array: any[] = (value as any[]).map(this.admit, this); // Arrays are looked up in the Trie using their recursively // canonicalized elements, and the known version of the array is // preserved as node.array. @@ -134,7 +134,7 @@ export class ObjectCanon { array.push(keys.json); const firstValueIndex = array.length; keys.sorted.forEach(key => { - array.push(this.admit(value[key])); + array.push(this.admit((value as any)[key])); }); // Objects are looked up in the Trie by their prototype (which // is *not* recursively canonicalized), followed by a JSON @@ -193,3 +193,24 @@ type SortedKeysInfo = { sorted: string[]; json: string; }; + +const stringifyCanon = new ObjectCanon; +const stringifyCache = new WeakMap(); + +// Since the keys of canonical objects are always created in lexicographically +// sorted order, we can use the ObjectCanon to implement a fast and stable +// version of JSON.stringify, which automatically sorts object keys. +export function canonicalStringify(value: any): string { + if (isObjectOrArray(value)) { + const canonical = stringifyCanon.admit(value); + let json = stringifyCache.get(canonical); + if (json === void 0) { + stringifyCache.set( + canonical, + json = JSON.stringify(canonical), + ); + } + return json; + } + return JSON.stringify(value); +} diff --git a/src/cache/inmemory/policies.ts b/src/cache/inmemory/policies.ts index f0a92829522..749b8af893f 100644 --- a/src/cache/inmemory/policies.ts +++ b/src/cache/inmemory/policies.ts @@ -48,6 +48,12 @@ import { } from '../core/types/common'; import { WriteContext } from './writeToStore'; +// Upgrade to a faster version of the default stable JSON.stringify function +// used by getStoreKeyName. This function is used when computing storeFieldName +// strings (when no keyArgs has been configured for a field). +import { canonicalStringify } from './object-canon'; +getStoreKeyName.setStringify(canonicalStringify); + export type TypePolicies = { [__typename: string]: TypePolicy; }