1
import {ObjectLiteral} from "../common/ObjectLiteral";
2

3 10
export class OrmUtils {
4

5
    // -------------------------------------------------------------------------
6
    // Public methods
7
    // -------------------------------------------------------------------------
8

9
    /**
10
     * Chunks array into peaces.
11
     */
12 10
    static chunk<T>(array: T[], size: number): T[][] {
13 6
        return Array.from(Array(Math.ceil(array.length / size)), (_, i) => {
14 6
            return array.slice(i * size, i * size + size);
15
        });
16
    }
17

18 10
    static splitClassesAndStrings<T>(clsesAndStrings: (string | T)[]): [T[], string[]] {
19 10
        return [
20 10
            (clsesAndStrings).filter((cls): cls is T => typeof cls !== "string"),
21 10
            (clsesAndStrings).filter((str): str is string => typeof str === "string"),
22
        ];
23
    }
24

25 10
    static groupBy<T, R>(array: T[], propertyCallback: (item: T) => R): { id: R, items: T[] }[] {
26 0
        return array.reduce((groupedArray, value) => {
27 0
            const key = propertyCallback(value);
28 0
            let grouped = groupedArray.find(i => i.id === key);
29 0
            if (!grouped) {
30 0
                grouped = { id: key, items: [] };
31 0
                groupedArray.push(grouped);
32
            }
33 0
            grouped.items.push(value);
34 0
            return groupedArray;
35
        }, [] as Array<{ id: R, items: T[] }>);
36
    }
37

38
    static uniq<T>(array: T[], criteria?: (item: T) => any): T[];
39
    static uniq<T, K extends keyof T>(array: T[], property: K): T[];
40 10
    static uniq<T, K extends keyof T>(array: T[], criteriaOrProperty?: ((item: T) => any) | K): T[] {
41 10
        return array.reduce((uniqueArray, item) => {
42 10
            let found: boolean = false;
43 10
            if (criteriaOrProperty instanceof Function) {
44 10
                const itemValue = criteriaOrProperty(item);
45 10
                found = !!uniqueArray.find(uniqueItem => criteriaOrProperty(uniqueItem) === itemValue);
46

47 0
            } else if (typeof criteriaOrProperty === "string") {
48 0
                found = !!uniqueArray.find(uniqueItem => uniqueItem[criteriaOrProperty] === item[criteriaOrProperty]);
49

50
            } else {
51 0
                found = uniqueArray.indexOf(item) !== -1;
52
            }
53

54 10
            if (!found)
55 10
                uniqueArray.push(item);
56

57 10
            return uniqueArray;
58
        }, [] as T[]);
59
    }
60

61 10
    static isObject(item: any) {
62 10
        return (item && typeof item === "object" && !Array.isArray(item));
63
    }
64

65
    /**
66
     * Deep Object.assign.
67
     *
68
     * @see http://stackoverflow.com/a/34749873
69
     */
70 10
    static mergeDeep(target: any, ...sources: any[]): any {
71 10
        if (!sources.length) return target;
72 10
        const source = sources.shift();
73

74 10
        if (this.isObject(target) && this.isObject(source)) {
75 10
            for (const key in source) {
76 10
                const value = source[key];
77 10
                if (key === "__proto__" || value instanceof Promise)
78 0
                    continue;
79

80 10
                if (this.isObject(value)
81 10
                && !(value instanceof Map)
82 10
                && !(value instanceof Set)
83 10
                && !(value instanceof Date)
84 10
                && !(value instanceof Buffer)
85 10
                && !(value instanceof RegExp)) {
86 10
                    if (!target[key])
87 10
                        Object.assign(target, { [key]: Object.create(Object.getPrototypeOf(value)) });
88 10
                    this.mergeDeep(target[key], value);
89
                } else {
90 10
                    Object.assign(target, { [key]: value });
91
                }
92
            }
93
        }
94

95 10
        return this.mergeDeep(target, ...sources);
96
    }
97

98
    /**
99
     * Deep compare objects.
100
     *
101
     * @see http://stackoverflow.com/a/1144249
102
     */
103 10
    static deepCompare(...args: any[]): boolean {
104
        let i: any, l: any, leftChain: any, rightChain: any;
105

106 10
        if (arguments.length < 1) {
107 0
            return true; // Die silently? Don't know how to handle such case, please help...
108
            // throw "Need two or more arguments to compare";
109
        }
110

111 10
        for (i = 1, l = arguments.length; i < l; i++) {
112

113 10
            leftChain = []; // Todo: this can be cached
114 10
            rightChain = [];
115

116 10
            if (!this.compare2Objects(leftChain, rightChain, arguments[0], arguments[i])) {
117 10
                return false;
118
            }
119
        }
120

121 10
        return true;
122
    }
123

124
    /**
125
     * Check if two entity-id-maps are the same
126
     */
127 10
    static compareIds(firstId: ObjectLiteral|undefined, secondId: ObjectLiteral|undefined): boolean {
128 10
        if (firstId === undefined || firstId === null || secondId === undefined || secondId === null)
129 10
            return false;
130

131
        // Optimized version for the common case
132 10
        if (
133 10
            ((typeof firstId.id === "string" && typeof secondId.id === "string") ||
134 10
            (typeof firstId.id === "number" && typeof secondId.id === "number")) &&
135 10
            Object.keys(firstId).length === 1 &&
136 10
            Object.keys(secondId).length === 1
137
        ) {
138 10
            return firstId.id === secondId.id;
139
        }
140

141 10
        return OrmUtils.deepCompare(firstId, secondId);
142
    }
143

144
    /**
145
     * Transforms given value into boolean value.
146
     */
147 10
    static toBoolean(value: any): boolean {
148 10
        if (typeof value === "boolean")
149 0
            return value;
150

151 10
        if (typeof value === "string")
152 0
            return value === "true" || value === "1";
153

154 10
        if (typeof value === "number")
155 0
            return value > 0;
156

157 10
        return false;
158
    }
159

160
    /**
161
     * Composes an object from the given array of keys and values.
162
     */
163 10
    static zipObject(keys: any[], values: any[]): ObjectLiteral {
164 0
        return keys.reduce((object, column, index) => {
165 0
            object[column] = values[index];
166 0
            return object;
167
        }, {} as ObjectLiteral);
168
    }
169

170
    /**
171
     * Compares two arrays.
172
     */
173 10
    static isArraysEqual(arr1: any[], arr2: any[]): boolean {
174 6
        if (arr1.length !== arr2.length) return false;
175 6
        return arr1.every(element => {
176 6
            return arr2.indexOf(element) !== -1;
177
        });
178
    }
179

180
    // -------------------------------------------------------------------------
181
    // Private methods
182
    // -------------------------------------------------------------------------
183

184 10
    private static compare2Objects(leftChain: any, rightChain: any, x: any, y: any) {
185
        let p;
186

187
        // remember that NaN === NaN returns false
188
        // and isNaN(undefined) returns true
189 10
        if (Number.isNaN(x) && Number.isNaN(y))
190 0
            return true;
191

192
        // Compare primitives and functions.
193
        // Check if both arguments link to the same object.
194
        // Especially useful on the step where we compare prototypes
195 10
        if (x === y)
196 10
            return true;
197

198
        // Unequal, but either is null or undefined (use case: jsonb comparasion)
199
        // PR #3776, todo: add tests
200 10
        if (x === null || y === null || x === undefined || y === undefined)
201 0
          return false;
202

203
        // Fix the buffer compare bug.
204
        // See: https://github.com/typeorm/typeorm/issues/3654
205 10
        if ((typeof x.equals === "function" || x.equals instanceof Function) && x.equals(y))
206 6
            return true;
207

208
        // Works in case when functions are created in constructor.
209
        // Comparing dates is a common scenario. Another built-ins?
210
        // We can even handle functions passed across iframes
211 10
        if ((typeof x === "function" && typeof y === "function") ||
212 10
            (x instanceof Date && y instanceof Date) ||
213 10
            (x instanceof RegExp && y instanceof RegExp) ||
214 10
            (x instanceof String && y instanceof String) ||
215 10
            (x instanceof Number && y instanceof Number))
216 0
            return x.toString() === y.toString();
217

218
        // At last checking prototypes as good as we can
219 10
        if (!(x instanceof Object && y instanceof Object))
220 10
            return false;
221

222 10
        if (x.isPrototypeOf(y) || y.isPrototypeOf(x))
223 0
            return false;
224

225 10
        if (x.constructor !== y.constructor)
226 0
            return false;
227

228 10
        if (x.prototype !== y.prototype)
229 0
            return false;
230

231
        // Check for infinitive linking loops
232 10
        if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1)
233 0
            return false;
234

235
        // Quick checking of one object being a subset of another.
236
        // todo: cache the structure of arguments[0] for performance
237 10
        for (p in y) {
238 10
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
239 6
                return false;
240
            }
241 10
            else if (typeof y[p] !== typeof x[p]) {
242 10
                return false;
243
            }
244
        }
245

246 10
        for (p in x) {
247 10
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
248 0
                return false;
249
            }
250 10
            else if (typeof y[p] !== typeof x[p]) {
251 0
                return false;
252
            }
253

254 10
            switch (typeof (x[p])) {
255 10
                case "object":
256 10
                case "function":
257

258 10
                    leftChain.push(x);
259 10
                    rightChain.push(y);
260

261 10
                    if (!this.compare2Objects(leftChain, rightChain, x[p], y[p])) {
262 0
                        return false;
263
                    }
264

265 10
                    leftChain.pop();
266 10
                    rightChain.pop();
267 10
                    break;
268

269 10
                default:
270 10
                    if (x[p] !== y[p]) {
271 10
                        return false;
272
                    }
273 10
                    break;
274
            }
275
        }
276

277 10
        return true;
278
    }
279

280 10
}

Read our documentation on viewing source code .

Loading