@decaf-ts/decorator-validation
Version:
simple decorator based validation engine
163 lines • 5.78 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.isEqual = isEqual;
/**
* @description Enhanced algorithm for deep comparison of any two values with optional ignored properties
* @summary Performs a deep equality check between two values, handling various types including primitives, objects, arrays, dates, and more
* @param {unknown} a - First value to compare
* @param {unknown} b - Second value to compare
* @param {string[]} propsToIgnore - A list of property names to ignore during comparison
* @return {boolean} Returns true if the values are deeply equal, false otherwise
* @function isEqual
* @mermaid
* sequenceDiagram
* participant Caller
* participant isEqual
* participant Recursion
*
* Caller->>isEqual: isEqual(a, b, propsToIgnore)
* Note over isEqual: Check simple cases (identity, null, primitives)
*
* alt a === b
* isEqual-->>Caller: true (with special case for +0/-0)
* else a or b is null
* isEqual-->>Caller: a === b
* else different types
* isEqual-->>Caller: false
* else both NaN
* isEqual-->>Caller: true
* else primitive types
* isEqual-->>Caller: a === b
* else both Date objects
* isEqual-->>Caller: Compare timestamps
* else both RegExp objects
* isEqual-->>Caller: Compare string representations
* else both Error objects
* isEqual-->>Caller: Compare name and message
* else both Arrays
* Note over isEqual: Check length
* loop For each element
* isEqual->>Recursion: isEqual(a[i], b[i], propsToIgnore)
* end
* else both Maps or Sets
* Note over isEqual: Compare size and entries
* else both TypedArrays
* Note over isEqual: Compare byte by byte
* else both Objects
* Note over isEqual: Filter keys by propsToIgnore
* Note over isEqual: Compare key counts
* loop For each key
* isEqual->>Recursion: isEqual(a[key], b[key], propsToIgnore)
* end
* Note over isEqual: Check Symbol properties
* Note over isEqual: Compare prototypes
* end
*
* isEqual-->>Caller: Comparison result
* @memberOf module:decorator-validation
*/
function isEqual(a, b, ...propsToIgnore) {
// Handle simple cases
if (a === b) {
// Special case for +0 and -0
return a !== 0 || 1 / a === 1 / b;
}
if (a == null || b == null)
return a === b;
if (typeof a !== typeof b)
return false;
// Handle NaN
if (Number.isNaN(a) && Number.isNaN(b))
return true;
// Handle primitive types
if (typeof a !== "object")
return a === b;
// Handle Date objects
if (a instanceof Date && b instanceof Date) {
// Check if both dates are invalid
if (isNaN(a.getTime()) && isNaN(b.getTime()))
return true;
return a.getTime() === b.getTime();
}
// Handle RegExp objects
if (a instanceof RegExp && b instanceof RegExp)
return a.toString() === b.toString();
// Handle Error objects
if (a instanceof Error && b instanceof Error) {
return a.name === b.name && a.message === b.message;
}
// Handle Array objects
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length)
return false;
for (let i = 0; i < a.length; i++) {
if (!isEqual(a[i], b[i], ...propsToIgnore))
return false;
}
return true;
}
// Handle Map objects
if (a instanceof Map && b instanceof Map) {
if (a.size !== b.size)
return false;
for (const [key, value] of a) {
if (!b.has(key) || !isEqual(value, b.get(key), ...propsToIgnore))
return false;
}
return true;
}
// Handle Set objects
if (a instanceof Set && b instanceof Set) {
if (a.size !== b.size)
return false;
for (const item of a) {
if (!b.has(item))
return false;
}
return true;
}
// Handle TypedArray objects
if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
if (a.byteLength !== b.byteLength)
return false;
if (a.byteOffset !== b.byteOffset)
return false;
if (a.buffer.byteLength !== b.buffer.byteLength)
return false;
const uint8A = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
const uint8B = new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
for (let i = 0; i < uint8A.length; i++) {
if (uint8A[i] !== uint8B[i])
return false;
}
return true;
}
// Handle other objects
const aKeys = Object.keys(a).filter((k) => !propsToIgnore.includes(k));
const bKeys = Object.keys(b).filter((k) => !propsToIgnore.includes(k));
if (aKeys.length !== bKeys.length)
return false;
for (const key of aKeys) {
if (!bKeys.includes(key))
return false;
if (!isEqual(a[key], b[key], ...propsToIgnore))
return false;
}
// Handle Symbol properties
const aSymbols = Object.getOwnPropertySymbols(a).filter((s) => !propsToIgnore.includes(s.toString()));
const bSymbols = Object.getOwnPropertySymbols(b).filter((s) => !propsToIgnore.includes(s.toString()));
if (aSymbols.length !== bSymbols.length)
return false;
for (const sym of aSymbols) {
if (!bSymbols.includes(sym))
return false;
if (!isEqual(a[sym], b[sym], ...propsToIgnore))
return false;
}
// Add this check right before the final return statement
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) {
return false;
}
return true;
}
//# sourceMappingURL=equality.js.map