// @denoify-line-ignore import { Polyfill as Set } from "minimal-polyfills/Set"; // @denoify-line-ignore import "minimal-polyfills/Object.is"; import { MapLike, SetLike, DateLike, ArrayLike } from "./types"; import { arrAllEquals } from "../reducers/allEquals"; /** * Function that perform a in depth comparison of two things of arbitrary type T * to see if they represent the same date regardless of object references. * * Think of it as JSON.stringify(o1) === JSON.stringify(o2) * but unlike a test performed with JSON.stringify the order in the property * have been assigned to an object does not matter and circular references are supported. * * * If takeIntoAccountArraysOrdering === false then * representsSameData(["a", "b"], ["b", "a"]) will return true. * * If Date are compared via .getTime() * * The objects can includes Map and Set. * */ export const same = (() => { function sameRec( o1: T, o2: T, { takeIntoAccountArraysOrdering }: { takeIntoAccountArraysOrdering: boolean } = { "takeIntoAccountArraysOrdering": true }, o1Path: { key: string; obj: any; }[], o2Path: { key: string; obj: any; }[], o1RealRef: T = o1, o2RealRef: T = o2 ): boolean { if (Object.is(o1, o2)) { return true; } { const i1 = o1Path.map(({ obj }) => obj).indexOf(o1RealRef); if (i1 >= 0) { const i2 = o2Path.map(({ obj }) => obj).indexOf(o2RealRef); if (i1 !== i2) { return false; } return arrAllEquals( [o1Path, o2Path] .map( oPath => oPath .map(({ key }) => key) .join("") ) ); } } if (!(o1 instanceof Object && o2 instanceof Object)) { return false; } if( typeof o1 === "function" || typeof o2 === "function" ){ return false; } if (DateLike.match(o1)) { if (!DateLike.match(o2)) { return false; } return o1.getTime() === o2!.getTime(); } if (MapLike.match(o1)) { if (!MapLike.match(o2)) { return false; } type Entry = { key: any, value: any }; const newO1 = new Set(); const newO2 = new Set(); for (const o of [o1, o2]) { const newO = o === o1 ? newO1 : newO2; const arr = Array.from(o.keys()); for (let i = 0; i < arr.length; i++) { const key = arr[i]; const value = o.get(key)!; newO.add({ key, value }); } } return sameRec( newO1, newO2, { takeIntoAccountArraysOrdering }, o1Path, o2Path, o1RealRef as any, o2RealRef ); } let takeIntoAccountArraysOrderingOv: false | undefined = undefined; if (SetLike.match(o1)) { if (!SetLike.match(o2)) { return false; } o1 = Array.from(o1.values()) as any; o2 = Array.from(o2.values()) as any; takeIntoAccountArraysOrderingOv = false } if (ArrayLike.match(o1)) { if (!ArrayLike.match(o2)) { return false; } if (o1.length !== o2.length) { return false; } if ( !(takeIntoAccountArraysOrderingOv !== undefined ? takeIntoAccountArraysOrderingOv : takeIntoAccountArraysOrdering) ) { const o2Set = new Set(Array.from(o2)); for (let i = 0; i < o1.length; i++) { if (!(`${i}` in o1)) { continue; } const val1 = o1[i]; if (o2Set.has(val1)) { o2Set.delete(val1); continue; } let isFound = false; for (const val2 of o2Set.values()) { if (!sameRec( val1, val2, { takeIntoAccountArraysOrdering }, [...o1Path, { "obj": o1RealRef, "key": "*" }], [...o2Path, { "obj": o2RealRef, "key": "*" }] )) { continue; } isFound = true; o2Set.delete(val2); break; } if (!isFound) { return false; } } return true; } //continue } else if (!sameRec( Object.keys(o1).filter(key => (o1 as any)[key] !== undefined), Object.keys(o2).filter(key => (o2 as any)[key] !== undefined), { "takeIntoAccountArraysOrdering": false }, [], [] )) { return false } for (const key in o1) { if (!sameRec( o1[key], o2[key], { takeIntoAccountArraysOrdering }, [...o1Path, { "obj": o1RealRef, key }], [...o2Path, { "obj": o2RealRef, key }] )) { return false; } } return true; } return function same( o1: T, o2: T, { takeIntoAccountArraysOrdering }: { takeIntoAccountArraysOrdering: boolean } = { "takeIntoAccountArraysOrdering": true }, ): boolean { return sameRec(o1, o2, { takeIntoAccountArraysOrdering }, [], []); } })(); /** * Return the "same" function with "takeIntoAccountArraysOrdering" default value set as desired. * */ export function sameFactory({ takeIntoAccountArraysOrdering }: { takeIntoAccountArraysOrdering: boolean; }) { return { "same": ( o1: T, o2: T, prop: { takeIntoAccountArraysOrdering: boolean } = { takeIntoAccountArraysOrdering }, ) => same(o1, o2, prop) } }