decycle
Version:
JSON decycle replaces circular references with JSON path references
1 lines • 4.58 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../src/decycle.ts"],"sourcesContent":["type ReplacerFunction = (value: unknown) => unknown;\n\ninterface DecycledObject {\n [key: string]: unknown;\n $ref?: string;\n}\n\n/**\n * Makes a deep copy of an object or array, assuring that there is at most\n * one instance of each object or array in the resulting structure. The\n * duplicate references (which might be forming cycles) are replaced with\n * an object of the form `{\"$ref\": PATH}` where the PATH is a JSONPath\n * string that locates the first occurance.\n *\n * @example\n * ```ts\n * var a = [];\n * a[0] = a;\n * JSON.stringify(decycle(a)); // '[{\"$ref\":\"$\"}]'\n * ```\n *\n * If a replacer function is provided, then it will be called for each value.\n * A replacer function receives a value and returns a replacement value.\n *\n * JSONPath is used to locate the unique object. `$` indicates the top level of\n * the object or array. `[NUMBER]` or `[STRING]` indicates a child element or\n * property.\n *\n * @param value - The object or array to decycle.\n * @param replacer - Optional replacer function called for each value.\n * @returns - A deep copy of the object with circular references replaced by `$ref` objects.\n */\nexport function decycle(value: unknown, replacer?: ReplacerFunction) {\n const visitedObjects = new WeakMap<object, string>();\n\n return deepCopy(value, '$', visitedObjects, replacer);\n}\n\n/**\n * Recursively deep copies a value, replacing circular references with\n * `{\"$ref\": PATH}` objects.\n *\n * @param value - The current value to copy.\n * @param path - The JSONPath to the current value.\n * @param visitedObjects - WeakMap tracking already-visited objects and their paths.\n * @param replacer - Optional replacer function called for each value.\n * @returns - The deep-copied value.\n */\nfunction deepCopy(\n value: unknown,\n path: string,\n visitedObjects: WeakMap<object, string>,\n replacer?: ReplacerFunction,\n): unknown {\n if (typeof replacer === 'function') {\n value = replacer(value);\n }\n\n if (!isPlainObjectOrArray(value)) {\n return value;\n }\n\n const existingPath = visitedObjects.get(value);\n\n if (existingPath !== undefined) {\n return { $ref: existingPath };\n }\n\n visitedObjects.set(value, path);\n\n if (Array.isArray(value)) {\n const copy: unknown[] = [];\n\n for (const [index, element] of value.entries()) {\n const newPath = `${path}[${index.toString()}]`;\n copy[index] = deepCopy(element, newPath, visitedObjects, replacer);\n }\n\n return copy;\n }\n\n const record = value as Record<string, unknown>;\n const copy: DecycledObject = {};\n\n for (const key of Object.keys(record)) {\n const newPath = `${path}[${JSON.stringify(key)}]`;\n copy[key] = deepCopy(record[key], newPath, visitedObjects, replacer);\n }\n\n return copy;\n}\n\n/**\n * Checks whether a value is a plain object or array (not a primitive or\n * built-in wrapper like `Boolean`, `Date`, `Number`, `RegExp`, or `String`).\n *\n * @param value - The value to check.\n * @returns - `true` if the value is a plain object or array.\n */\nfunction isPlainObjectOrArray(value: unknown): value is object {\n return (\n typeof value === 'object' &&\n value !== null &&\n !(value instanceof Boolean) &&\n !(value instanceof Date) &&\n !(value instanceof Number) &&\n !(value instanceof RegExp) &&\n !(value instanceof String)\n );\n}\n"],"names":["decycle","value","replacer","deepCopy","path","visitedObjects","isPlainObjectOrArray","existingPath","copy","index","element","newPath","record","key"],"mappings":"AAgCO,SAASA,EAAQC,GAAgBC,GAA6B;AAGnE,SAAOC,EAASF,GAAO,yBAFI,QAAA,GAEiBC,CAAQ;AACtD;AAYA,SAASC,EACPF,GACAG,GACAC,GACAH,GACS;AAKT,MAJI,OAAOA,KAAa,eACtBD,IAAQC,EAASD,CAAK,IAGpB,CAACK,EAAqBL,CAAK;AAC7B,WAAOA;AAGT,QAAMM,IAAeF,EAAe,IAAIJ,CAAK;AAE7C,MAAIM,MAAiB;AACnB,WAAO,EAAE,MAAMA,EAAA;AAKjB,MAFAF,EAAe,IAAIJ,GAAOG,CAAI,GAE1B,MAAM,QAAQH,CAAK,GAAG;AACxB,UAAMO,IAAkB,CAAA;AAExB,eAAW,CAACC,GAAOC,CAAO,KAAKT,EAAM,WAAW;AAC9C,YAAMU,IAAU,GAAGP,CAAI,IAAIK,EAAM,UAAU;AAC3CD,MAAAA,EAAKC,CAAK,IAAIN,EAASO,GAASC,GAASN,GAAgBH,CAAQ;AAAA,IACnE;AAEA,WAAOM;AAAAA,EACT;AAEA,QAAMI,IAASX,GACTO,IAAuB,CAAA;AAE7B,aAAWK,KAAO,OAAO,KAAKD,CAAM,GAAG;AACrC,UAAMD,IAAU,GAAGP,CAAI,IAAI,KAAK,UAAUS,CAAG,CAAC;AAC9C,IAAAL,EAAKK,CAAG,IAAIV,EAASS,EAAOC,CAAG,GAAGF,GAASN,GAAgBH,CAAQ;AAAA,EACrE;AAEA,SAAOM;AACT;AASA,SAASF,EAAqBL,GAAiC;AAC7D,SACE,OAAOA,KAAU,YACjBA,MAAU,QACV,EAAEA,aAAiB,YACnB,EAAEA,aAAiB,SACnB,EAAEA,aAAiB,WACnB,EAAEA,aAAiB,WACnB,EAAEA,aAAiB;AAEvB;"}