UNPKG

remeda

Version:

A utility library for JavaScript and Typescript.

1 lines 8.65 kB
{"version":3,"file":"isDeepEqual.cjs","names":["purry"],"sources":["../src/isDeepEqual.ts"],"sourcesContent":["import { purry } from \"./purry\";\n\n/**\n * Performs a *deep structural* comparison between two values to determine if\n * they are equivalent. For primitive values this is equivalent to `===`, for\n * arrays the check would be performed on every item recursively, in order, and\n * for objects all props will be compared recursively.\n *\n * The built-in Date and RegExp are special-cased and will be compared by their\n * values.\n *\n * !IMPORTANT: TypedArrays and symbol properties of objects are not supported\n * right now and might result in unexpected behavior. Please open an issue in\n * the Remeda github project if you need support for these types.\n *\n * The result would be narrowed to the second value so that the function can be\n * used as a type guard.\n *\n * See:\n * - `isStrictEqual` if you don't need a deep comparison and just want to\n * check for simple (`===`, `Object.is`) equality.\n * - `isShallowEqual` if you need to compare arrays and objects \"by-value\" but\n * don't want to recurse into their values.\n *\n * @param data - The first value to compare.\n * @param other - The second value to compare.\n * @signature\n * R.isDeepEqual(data, other)\n * @example\n * R.isDeepEqual(1, 1) //=> true\n * R.isDeepEqual(1, '1') //=> false\n * R.isDeepEqual([1, 2, 3], [1, 2, 3]) //=> true\n * @dataFirst\n * @category Guard\n */\nexport function isDeepEqual<T, S extends T>(\n data: T,\n other: T extends Exclude<T, S> ? S : never,\n): data is S;\nexport function isDeepEqual<T>(data: T, other: T): boolean;\n\n/**\n * Performs a *deep structural* comparison between two values to determine if\n * they are equivalent. For primitive values this is equivalent to `===`, for\n * arrays the check would be performed on every item recursively, in order, and\n * for objects all props will be compared recursively.\n *\n * The built-in Date and RegExp are special-cased and will be compared by their\n * values.\n *\n * !IMPORTANT: TypedArrays and symbol properties of objects are not supported\n * right now and might result in unexpected behavior. Please open an issue in\n * the Remeda github project if you need support for these types.\n *\n * The result would be narrowed to the second value so that the function can be\n * used as a type guard.\n *\n * See:\n * - `isStrictEqual` if you don't need a deep comparison and just want to\n * check for simple (`===`, `Object.is`) equality.\n * - `isShallowEqual` if you need to compare arrays and objects \"by-value\" but\n * don't want to recurse into their values.\n *\n * @param other - The second value to compare.\n * @signature\n * R.isDeepEqual(other)(data)\n * @example\n * R.pipe(1, R.isDeepEqual(1)); //=> true\n * R.pipe(1, R.isDeepEqual('1')); //=> false\n * R.pipe([1, 2, 3], R.isDeepEqual([1, 2, 3])); //=> true\n * @dataLast\n * @category Guard\n */\nexport function isDeepEqual<T, S extends T>(\n other: T extends Exclude<T, S> ? S : never,\n): (data: T) => data is S;\nexport function isDeepEqual<T>(other: T): (data: T) => boolean;\n\nexport function isDeepEqual(...args: ReadonlyArray<unknown>): unknown {\n return purry(isDeepEqualImplementation, args);\n}\n\nfunction isDeepEqualImplementation<T>(data: unknown, other: T): data is T {\n if (data === other) {\n return true;\n }\n\n if (Object.is(data, other)) {\n // We want to ignore the slight differences between `===` and `Object.is` as\n // both of them largely define equality from a semantic point-of-view.\n return true;\n }\n\n if (typeof data !== \"object\" || typeof other !== \"object\") {\n return false;\n }\n\n if (data === null || other === null) {\n return false;\n }\n\n if (Object.getPrototypeOf(data) !== Object.getPrototypeOf(other)) {\n // If the objects don't share a prototype it's unlikely that they are\n // semantically equal. It is technically possible to build 2 prototypes that\n // act the same but are not equal (at the reference level, checked via\n // `===`) and then create 2 objects that are equal although we would fail on\n // them. Because this is so unlikely, the optimization we gain here for the\n // rest of the function by assuming that `other` is of the same type as\n // `data` is more than worth it.\n return false;\n }\n\n if (Array.isArray(data)) {\n return isDeepEqualArrays(data, other as unknown as ReadonlyArray<unknown>);\n }\n\n if (data instanceof Map) {\n return isDeepEqualMaps(data, other as unknown as Map<unknown, unknown>);\n }\n\n if (data instanceof Set) {\n return isDeepEqualSets(data, other as unknown as Set<unknown>);\n }\n\n if (data instanceof Date) {\n return data.getTime() === (other as unknown as Date).getTime();\n }\n\n if (data instanceof RegExp) {\n return data.toString() === (other as unknown as RegExp).toString();\n }\n\n // At this point we only know that the 2 objects share a prototype and are not\n // any of the previous types. They could be plain objects (Object.prototype),\n // they could be classes, they could be other built-ins, or they could be\n // something weird. We assume that comparing values by keys is enough to judge\n // their equality.\n\n if (Object.keys(data).length !== Object.keys(other).length) {\n return false;\n }\n\n for (const [key, value] of Object.entries(data)) {\n if (!(key in other)) {\n return false;\n }\n\n if (\n !isDeepEqualImplementation(\n value,\n // @ts-expect-error [ts7053] - We already checked that `other` has `key`\n other[key],\n )\n ) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction isDeepEqualArrays(\n data: ReadonlyArray<unknown>,\n other: ReadonlyArray<unknown>,\n): boolean {\n if (data.length !== other.length) {\n return false;\n }\n\n for (const [index, item] of data.entries()) {\n if (!isDeepEqualImplementation(item, other[index])) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction isDeepEqualMaps(\n data: ReadonlyMap<unknown, unknown>,\n other: ReadonlyMap<unknown, unknown>,\n): boolean {\n if (data.size !== other.size) {\n return false;\n }\n\n for (const [key, value] of data.entries()) {\n if (!other.has(key)) {\n return false;\n }\n\n if (!isDeepEqualImplementation(value, other.get(key))) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction isDeepEqualSets(\n data: ReadonlySet<unknown>,\n other: ReadonlySet<unknown>,\n): boolean {\n if (data.size !== other.size) {\n return false;\n }\n\n // To ensure we only count each item once we need to \"remember\" which items of\n // the other set we've already matched against. We do this by creating a copy\n // of the other set and removing items from it as we find them in the data\n // set.\n const otherCopy = [...other];\n\n for (const dataItem of data) {\n let isFound = false;\n\n for (const [index, otherItem] of otherCopy.entries()) {\n if (isDeepEqualImplementation(dataItem, otherItem)) {\n isFound = true;\n otherCopy.splice(index, 1);\n break;\n }\n }\n\n if (!isFound) {\n return false;\n }\n }\n\n return true;\n}\n"],"mappings":"wCA8EA,SAAgB,EAAY,GAAG,EAAuC,CACpE,OAAOA,EAAAA,EAAM,EAA2B,EAAK,CAG/C,SAAS,EAA6B,EAAe,EAAqB,CAKxE,GAJI,IAAS,GAIT,OAAO,GAAG,EAAM,EAAM,CAGxB,MAAO,GAWT,GARI,OAAO,GAAS,UAAY,OAAO,GAAU,UAI7C,IAAS,MAAQ,IAAU,MAI3B,OAAO,eAAe,EAAK,GAAK,OAAO,eAAe,EAAM,CAQ9D,MAAO,GAGT,GAAI,MAAM,QAAQ,EAAK,CACrB,OAAO,EAAkB,EAAM,EAA2C,CAG5E,GAAI,aAAgB,IAClB,OAAO,EAAgB,EAAM,EAA0C,CAGzE,GAAI,aAAgB,IAClB,OAAO,EAAgB,EAAM,EAAiC,CAGhE,GAAI,aAAgB,KAClB,OAAO,EAAK,SAAS,GAAM,EAA0B,SAAS,CAGhE,GAAI,aAAgB,OAClB,OAAO,EAAK,UAAU,GAAM,EAA4B,UAAU,CASpE,GAAI,OAAO,KAAK,EAAK,CAAC,SAAW,OAAO,KAAK,EAAM,CAAC,OAClD,MAAO,GAGT,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,EAAK,CAK7C,GAJI,EAAE,KAAO,IAKX,CAAC,EACC,EAEA,EAAM,GACP,CAED,MAAO,GAIX,MAAO,GAGT,SAAS,EACP,EACA,EACS,CACT,GAAI,EAAK,SAAW,EAAM,OACxB,MAAO,GAGT,IAAK,GAAM,CAAC,EAAO,KAAS,EAAK,SAAS,CACxC,GAAI,CAAC,EAA0B,EAAM,EAAM,GAAO,CAChD,MAAO,GAIX,MAAO,GAGT,SAAS,EACP,EACA,EACS,CACT,GAAI,EAAK,OAAS,EAAM,KACtB,MAAO,GAGT,IAAK,GAAM,CAAC,EAAK,KAAU,EAAK,SAAS,CAKvC,GAJI,CAAC,EAAM,IAAI,EAAI,EAIf,CAAC,EAA0B,EAAO,EAAM,IAAI,EAAI,CAAC,CACnD,MAAO,GAIX,MAAO,GAGT,SAAS,EACP,EACA,EACS,CACT,GAAI,EAAK,OAAS,EAAM,KACtB,MAAO,GAOT,IAAM,EAAY,CAAC,GAAG,EAAM,CAE5B,IAAK,IAAM,KAAY,EAAM,CAC3B,IAAI,EAAU,GAEd,IAAK,GAAM,CAAC,EAAO,KAAc,EAAU,SAAS,CAClD,GAAI,EAA0B,EAAU,EAAU,CAAE,CAClD,EAAU,GACV,EAAU,OAAO,EAAO,EAAE,CAC1B,MAIJ,GAAI,CAAC,EACH,MAAO,GAIX,MAAO"}