-
Notifications
You must be signed in to change notification settings - Fork 0
/
object.util.ts
216 lines (184 loc) · 6.67 KB
/
object.util.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import { Matcher } from "../interface/matcher.interface";
/**
* Returns true if the values of the specified 'first' & 'second' argument are
* identical - that is, similar in every detail; exactly like. Keys are ordered
* for each object and the values corresponding to said keys are then compared.
*
* Considerations for this utility:
* Handling an object that references itself?
* Comparing arrays of arrays?
* Comparing functions?
*
* @param first to be compared against the 'second' argument for identicalness.
* @param second to be compared against the 'second' argument for identicalness.
*/
export function areIdentical<T>(first: T, second: T): boolean {
if (!first && !second) {
return first === second;
} else if (!first || !second) {
return false;
}
switch (typeof first) {
case "boolean":
case "number":
case "string":
case "undefined":
return first === second;
case "object":
return areIdenticalObjects(first, second);
}
return true;
}
/**
* Returns true if the specified object arguments are identical. In this case,
* identicalness is achieved by both arguments having the same keys and those
* same keys having identical corresponding values.
*
* @param first to be compared with the 'second' argument for identicalness.
* @param second to be compared with the 'second' argument for identicalness.
*/
function areIdenticalObjects<T>(first: T, second: T): boolean {
const firstKeys: string[] = Object.keys(first).sort();
const secondKeys: string[] = Object.keys(second).sort();
const firstKeysLength: number = firstKeys.length;
if (!areKeysValid(firstKeysLength, firstKeys, secondKeys)) {
return false;
}
return compareObjects(first, second, firstKeys, firstKeysLength);
}
/**
* Returns true if 'firstKeys' argument is equal to 'secondKeys' argument.
*
* @param firstKeysLength used to check if the lengths are equal.
* @param firstKeys to be compared against 'secondKeys'
* @param secondKeys to be compared against 'firstKeys'
* @return boolean value indicating whether the specified Arrays (keys) are equal.
*/
const areKeysValid = (firstKeysLength: number, firstKeys: string[], secondKeys: string[]): boolean => {
if (firstKeysLength !== secondKeys.length) {
return false;
}
for (let i: number = 0; i < firstKeysLength; i++) {
if (firstKeys[i] !== secondKeys[i]) {
return false;
}
}
return true;
};
const compareObjects = <T>(first: T, second: T, firstKeys: string[], firstKeysLength: number): boolean => {
for (let i: number = 0; i < firstKeysLength; i++) {
const key: string = firstKeys[i];
const firstValue: T = first[key];
const secondValue: T = second[key];
if (Array.isArray(firstValue) && Array.isArray(secondValue)) {
if (!areIdenticalArrays(firstValue, secondValue)) {
return false;
}
} else if (!areIdentical(firstValue, secondValue)) {
return false;
}
}
return true;
};
/**
* Returns true if the value of the specified argument is undefined.
*
* @param argument are you undefined?
*/
export function isUndefined(argument: unknown): boolean {
return argument === undefined;
}
/**
* Returns true if the value of the specified argument is null.
*
* @param argument are you null?
*/
export function isNull(argument: unknown): boolean {
return argument === null;
}
/**
* Returns true if the value of the specified argument is null or undefined.
*
* @param argument are you null or undefined?
*/
export function isNullOrUndefined(argument: unknown): boolean {
return isUndefined(argument) || isNull(argument);
}
/**
* We have isNaN, why do we not have isANumber?
*
* @param argument are you not not a (!isNaN...) number?
*/
export function isANumber(argument: unknown): boolean {
return typeof argument === "number";
}
/* array util. situated here because of circular dependency.
I would prefer to have it in its own file(array.util.ts) */
/**
* Returns true if the values of the two specified arrays are identical - that is,
* every element belonging to argument 'first', has a corresponding identical
* element belonging to argument 'second'.
*
* TODO: provide code examples of true and false results.
* TODO: Elaborate further on logic, if possible.
*
* @param first to be compared with the 'second' argument for identicalness.
* @param second to be compared with the 'first' argument for identicalness.
*/
export function areIdenticalArrays<T>(first: T[], second: T[]): boolean {
const length: number = first.length;
if (length !== second.length) {
return false;
}
const firstMatchers: Matcher<T>[] = buildMatcherList(first, length);
const secondMatchers: Matcher<T>[] = buildMatcherList(second, length);
compareArrayElements(firstMatchers, secondMatchers, length);
return isMatcherValid(firstMatchers, length) && isMatcherValid(secondMatchers, length);
}
const buildMatcherList = <T>(array: T[], length: number): Matcher<T>[] => {
const list: Matcher<T>[] = [];
for (let i: number = 0; i < length; i++) {
list.push({object: array[i], isIdentical: false});
}
return list;
};
/**
* Checks if each element in 'firstMatchers' argument has a corresponding identical
* element in 'secondMatchers' argument.
*
* @param firstMatchers to be compared with 'secondMatchers'
* @param secondMatchers to be compared with 'firstMatchers'
* @param length for iteration & caching purposes.
*/
const compareArrayElements = <T>(firstMatchers: Matcher<T>[], secondMatchers: Matcher<T>[], length: number): void => {
outer: for (let i: number = 0; i < length; i++) {
for (let innerI: number = 0; i < length; innerI++) {
const firstMatcher: Matcher<T> = firstMatchers[i];
const secondMatcher: Matcher<T> = secondMatchers[innerI];
if (secondMatcher.isIdentical) {
continue;
}
if (areIdentical(firstMatcher.object, secondMatcher.object)) {
firstMatcher.isIdentical = true;
secondMatcher.isIdentical = true;
continue outer;
}
}
}
};
/**
* Returns true if all Matcher elements have a value corresponding true value for
* 'isIdentical' field.
*
* @param matchers to be checked for validity.
* @param length for iteration & caching purposes.
*/
const isMatcherValid = <T>(matchers: Matcher<T>[], length: number): boolean => {
for (let i: number = 0; i < length; i++) {
if (!matchers[i].isIdentical) {
return false;
}
}
return true;
};
/* array util end */