import {ObjectLiteral} from "../common/ObjectLiteral"; export class OrmUtils { // ------------------------------------------------------------------------- // Public methods // ------------------------------------------------------------------------- /** * Chunks array into peaces. */ static chunk(array: T[], size: number): T[][] { return Array.from(Array(Math.ceil(array.length / size)), (_, i) => { return array.slice(i * size, i * size + size); }); } static splitClassesAndStrings(clsesAndStrings: (string|T)[]): [T[], string[]] { return [ (clsesAndStrings).filter((cls): cls is T => typeof cls !== "string"), (clsesAndStrings).filter((str): str is string => typeof str === "string"), ]; } static groupBy(array: T[], propertyCallback: (item: T) => R): { id: R, items: T[] }[] { return array.reduce((groupedArray, value) => { const key = propertyCallback(value); let grouped = groupedArray.find(i => i.id === key); if (!grouped) { grouped = { id: key, items: [] }; groupedArray.push(grouped); } grouped.items.push(value); return groupedArray; }, [] as Array<{ id: R, items: T[] }>); } static uniq(array: T[], criteria?: (item: T) => any): T[]; static uniq(array: T[], property: K): T[]; static uniq(array: T[], criteriaOrProperty?: ((item: T) => any)|K): T[] { return array.reduce((uniqueArray, item) => { let found: boolean = false; if (criteriaOrProperty instanceof Function) { const itemValue = criteriaOrProperty(item); found = !!uniqueArray.find(uniqueItem => criteriaOrProperty(uniqueItem) === itemValue); } else if (typeof criteriaOrProperty === "string") { found = !!uniqueArray.find(uniqueItem => uniqueItem[criteriaOrProperty] === item[criteriaOrProperty]); } else { found = uniqueArray.indexOf(item) !== -1; } if (!found) uniqueArray.push(item); return uniqueArray; }, [] as T[]); } static isObject(item: any) { return (item && typeof item === "object" && !Array.isArray(item)); } /** * Deep Object.assign. * * @see http://stackoverflow.com/a/34749873 */ static mergeDeep(target: any, ...sources: any[]): any { if (!sources.length) return target; const source = sources.shift(); if (this.isObject(target) && this.isObject(source)) { for (const key in source) { let propertyKey = key; if (source[key] instanceof Promise) continue; // if (source[key] instanceof Promise) { // propertyKey = "__" + key + "__"; // } if (this.isObject(source[propertyKey]) && !(source[propertyKey] instanceof Date) && !(source[propertyKey] instanceof Buffer)) { if (!target[key]) Object.assign(target, { [key]: {} }); this.mergeDeep(target[key], source[propertyKey]); } else { Object.assign(target, { [key]: source[propertyKey] }); } } } return this.mergeDeep(target, ...sources); } /** * Deep compare objects. * * @see http://stackoverflow.com/a/1144249 */ static deepCompare(...args: any[]) { let i: any, l: any, leftChain: any, rightChain: any; if (arguments.length < 1) { return true; // Die silently? Don't know how to handle such case, please help... // throw "Need two or more arguments to compare"; } for (i = 1, l = arguments.length; i < l; i++) { leftChain = []; // Todo: this can be cached rightChain = []; if (!this.compare2Objects(leftChain, rightChain, arguments[0], arguments[i])) { return false; } } return true; } /** * Transforms given value into boolean value. */ static toBoolean(value: any): boolean { if (typeof value === "boolean") return value; if (typeof value === "string") return value === "true" || value === "1"; if (typeof value === "number") return value > 0; return false; } /** * Composes an object from the given array of keys and values. */ static zipObject(keys: any[], values: any[]): ObjectLiteral { return keys.reduce((object, column, index) => { object[column] = values[index]; return object; }, {} as ObjectLiteral); } /** * Compares two arrays. */ static isArraysEqual(arr1: any[], arr2: any[]): boolean { if (arr1.length !== arr2.length) return false; return arr1.every(element => { return arr2.indexOf(element) !== -1; }); } // ------------------------------------------------------------------------- // Private methods // ------------------------------------------------------------------------- private static compare2Objects(leftChain: any, rightChain: any, x: any, y: any) { let p; // remember that NaN === NaN returns false // and isNaN(undefined) returns true if (isNaN(x) && isNaN(y) && typeof x === "number" && typeof y === "number") return true; // Compare primitives and functions. // Check if both arguments link to the same object. // Especially useful on the step where we compare prototypes if (x === y) return true; if (x.equals instanceof Function && x.equals(y)) return true; // Works in case when functions are created in constructor. // Comparing dates is a common scenario. Another built-ins? // We can even handle functions passed across iframes if ((typeof x === "function" && typeof y === "function") || (x instanceof Date && y instanceof Date) || (x instanceof RegExp && y instanceof RegExp) || (x instanceof String && y instanceof String) || (x instanceof Number && y instanceof Number)) return x.toString() === y.toString(); // At last checking prototypes as good as we can if (!(x instanceof Object && y instanceof Object)) return false; if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) return false; if (x.constructor !== y.constructor) return false; if (x.prototype !== y.prototype) return false; // Check for infinitive linking loops if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) return false; // Quick checking of one object being a subset of another. // todo: cache the structure of arguments[0] for performance for (p in y) { if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) { return false; } else if (typeof y[p] !== typeof x[p]) { return false; } } for (p in x) { if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) { return false; } else if (typeof y[p] !== typeof x[p]) { return false; } switch (typeof (x[p])) { case "object": case "function": leftChain.push(x); rightChain.push(y); if (!this.compare2Objects(leftChain, rightChain, x[p], y[p])) { return false; } leftChain.pop(); rightChain.pop(); break; default: if (x[p] !== y[p]) { return false; } break; } } return true; } }