Skip to content

Commit 5070c5e

Browse files
committed
Added combineObjects() and changed difference()
1 parent 7e2a7a1 commit 5070c5e

File tree

3 files changed

+135
-20
lines changed

3 files changed

+135
-20
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "lup-utils",
3-
"version": "1.2.0",
3+
"version": "1.2.2",
44
"description": "NodeJS library providing utility functions.",
55
"main": "./lib/index",
66
"types": "./lib/index.d.ts",

src/__tests__/object.test.ts

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,39 @@
1-
import { deepEqual, difference } from '../object';
1+
import { combineObjects, deepEqual, difference } from '../object';
2+
3+
test('combineObjects()', async () => {
4+
expect(combineObjects(undefined, undefined)).toBeUndefined();
5+
expect(combineObjects(undefined, 5)).toBe(5);
6+
expect(combineObjects(5, undefined)).toBeUndefined();
7+
expect(combineObjects(5, undefined, { ignoreUndefined: true })).toBe(5);
8+
expect(combineObjects(5, 10)).toBe(10);
9+
expect(combineObjects(5, 0, { ignoreUndefined: true })).toBe(0);
10+
11+
expect(combineObjects([1, 2], [3, 4])).toEqual([3, 4]);
12+
expect(combineObjects([1, 2], [3, 4], { mergeArrays: true })).toEqual([1, 2, 3, 4]);
13+
14+
expect(combineObjects({ a: 1, b: 2 }, { b: 3, c: 4 })).toEqual({ a: 1, b: 3, c: 4 });
15+
expect(combineObjects({ a: 1, b: 2 }, { b: undefined, c: 4 }, { ignoreUndefined: false })).toEqual({
16+
a: 1,
17+
b: undefined,
18+
c: 4,
19+
});
20+
expect(combineObjects({ a: 1, b: 2 }, { b: undefined, c: 4 }, { ignoreUndefined: true })).toEqual({
21+
a: 1,
22+
b: 2,
23+
c: 4,
24+
});
25+
26+
expect(combineObjects({ a: 1, b: { c: 5, d: 6 } }, { b: { e: 7 } }, { recursive: false })).toEqual({
27+
a: 1,
28+
b: { e: 7 },
29+
});
30+
expect(combineObjects({ a: 1, b: { c: 5, d: 6 } }, { b: { e: 7 } }, { recursive: true })).toEqual({
31+
a: 1,
32+
b: { c: 5, d: 6, e: 7 },
33+
});
34+
35+
expect(combineObjects({ a: [1, 2], b: 2 }, { a: [3, 4] }, { mergeArrays: true })).toEqual({ a: [1, 2, 3, 4], b: 2 });
36+
});
237

338
test('deepEqual()', async () => {
439
// Primitive values (true)
@@ -76,47 +111,62 @@ test('deepEqual()', async () => {
76111
});
77112

78113
test('difference()', async () => {
79-
expect(difference({}, {}, { recursive: false, ignoreOrder: false })).toEqual({});
80-
expect(difference({ a: 1 }, { a: 1 }, { recursive: false, ignoreOrder: false })).toEqual({});
81-
expect(difference({ a: 1 }, { a: 2 }, { recursive: false, ignoreOrder: false })).toEqual({ a: 2 });
82-
expect(difference({ a: 1, b: 2 }, { a: 1 }, { recursive: false, ignoreOrder: false })).toEqual({ b: undefined });
83-
expect(difference({ a: 1 }, { a: 1, b: 2 }, { recursive: false, ignoreOrder: false })).toEqual({ b: 2 });
114+
expect(difference({}, {}, { recursive: false, strictOrder: true })).toEqual({});
115+
expect(difference({ a: 1 }, { a: 1 }, { recursive: false, strictOrder: true })).toEqual({});
116+
expect(difference({ a: 1 }, { a: 2 }, { recursive: false, strictOrder: true })).toEqual({ a: 2 });
117+
expect(difference({ a: 1, b: 2 }, { a: 1 }, { recursive: false, strictOrder: true })).toEqual({ b: undefined });
118+
expect(difference({ a: 1 }, { a: 1, b: 2 }, { recursive: false, strictOrder: true })).toEqual({ b: 2 });
84119
expect(
85120
difference(
86121
{ a: { b: 2, c: { d: 3, e: 4 } } },
87122
{ a: { b: 2, c: { d: 3, e: 4 } } },
88-
{ recursive: false, ignoreOrder: false },
123+
{ recursive: false, strictOrder: true },
89124
),
90125
).toEqual({});
91126
expect(
92127
difference(
93128
{ a: { b: 2, c: { d: 3, e: 4 } } },
94129
{ a: { b: 3, c: { d: 3, e: 4 } } },
95-
{ recursive: false, ignoreOrder: false },
130+
{ recursive: false, strictOrder: true },
96131
),
97132
).toEqual({ a: { b: 3, c: { d: 3, e: 4 } } });
98133
expect(
99134
difference(
100135
{ a: { b: 2, c: { d: 3, e: 4 } } },
101136
{ a: { b: 2, c: { d: 3, e: 5 } } },
102-
{ recursive: false, ignoreOrder: false },
137+
{ recursive: false, strictOrder: true },
103138
),
104139
).toEqual({ a: { b: 2, c: { d: 3, e: 5 } } });
105-
expect(difference({ a: [1, 2, 3] }, { a: [1, 2, 3] }, { recursive: false, ignoreOrder: false })).toEqual({});
106-
expect(difference({ a: [1, 2, 3] }, { a: [3, 2, 1] }, { recursive: false, ignoreOrder: false })).toEqual({
140+
expect(difference({ a: [1, 2, 3] }, { a: [1, 2, 3] }, { recursive: false, strictOrder: true })).toEqual({});
141+
expect(difference({ a: [1, 2, 3] }, { a: [3, 2, 1] }, { recursive: false, strictOrder: true })).toEqual({
107142
a: [3, 2, 1],
108143
});
109-
expect(difference({ a: [1, 2, 3] }, { a: [4, 5, 6] }, { recursive: false, ignoreOrder: false })).toEqual({
144+
expect(difference({ a: [1, 2, 3] }, { a: [4, 5, 6] }, { recursive: false, strictOrder: true })).toEqual({
110145
a: [4, 5, 6],
111146
});
112147

113-
expect(difference({ a: [1, 2, 3] }, { a: [3, 2, 1] }, { recursive: false, ignoreOrder: true })).toEqual({});
148+
expect(difference({ a: [1, 2, 3] }, { a: [3, 2, 1] }, { recursive: false, strictOrder: false })).toEqual({});
114149

115150
expect(
116151
difference(
117152
{ a: { b: 2, c: { d: 3, e: 4 } } },
118153
{ a: { b: 2, c: { d: 3, e: 5 } } },
119-
{ recursive: true, ignoreOrder: false },
154+
{ recursive: true, strictOrder: true },
120155
),
121156
).toEqual({ a: { c: { e: 5 } } });
157+
158+
expect(
159+
difference(
160+
{ a: '1', b: '2', c: '3' },
161+
{ b: '2' },
162+
{ recursive: false, strictOrder: false, ignoreRemovedKeys: true },
163+
),
164+
).toEqual({});
165+
expect(
166+
difference(
167+
{ a: '1', b: '2', c: '3' },
168+
{ b: '2' },
169+
{ recursive: false, strictOrder: false, ignoreRemovedKeys: false },
170+
),
171+
).toEqual({ a: undefined, c: undefined });
122172
});

src/object.ts

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,64 @@
1+
export function combineObjects<O, N>(
2+
oldObj: O,
3+
newObj: N,
4+
options?: {
5+
/** If true, undefined values in the new object will be ignored and won't overwrite the old values. */
6+
ignoreUndefined?: boolean;
7+
8+
/** If true, arrays in the old and new object will be merged instead of replaced. */
9+
mergeArrays?: boolean;
10+
11+
/** If true, performs a deep merge of nested objects. */
12+
recursive?: boolean;
13+
},
14+
): O & N {
15+
if (Array.isArray(oldObj)) {
16+
if (Array.isArray(newObj)) {
17+
if (options?.mergeArrays) {
18+
return [...oldObj, ...newObj] as any;
19+
} else {
20+
return newObj as any;
21+
}
22+
} else {
23+
return (options?.ignoreUndefined && newObj === undefined ? oldObj : newObj) as any;
24+
}
25+
}
26+
27+
if (typeof oldObj !== 'object' || oldObj === null || typeof newObj !== 'object' || newObj === null) {
28+
return (options?.ignoreUndefined && newObj === undefined ? oldObj : newObj) as any;
29+
}
30+
31+
const result: any = { ...oldObj };
32+
for (const key of Object.keys(newObj)) {
33+
const newValue: any = (newObj as any)[key];
34+
const oldValue: any = (oldObj as any)[key];
35+
36+
if (newValue === undefined && options?.ignoreUndefined) {
37+
continue;
38+
}
39+
40+
if (options?.mergeArrays && Array.isArray(oldValue) && Array.isArray(newValue)) {
41+
result[key] = [...oldValue, ...newValue];
42+
continue;
43+
}
44+
45+
if (
46+
options?.recursive &&
47+
typeof oldValue === 'object' &&
48+
oldValue !== null &&
49+
typeof newValue === 'object' &&
50+
newValue !== null
51+
) {
52+
result[key] = combineObjects(oldValue, newValue, options);
53+
continue;
54+
}
55+
56+
result[key] = options?.ignoreUndefined && newValue === undefined ? oldValue : newValue;
57+
}
58+
59+
return result;
60+
}
61+
162
/**
263
* Deeply compares two values for equality.
364
* @param a Object to compare.
@@ -49,15 +110,18 @@ export function difference<O, N>(
49110
oldObj: O,
50111
newObj: N,
51112
options?: {
52-
/** Whether to ignore the order of items in arrays. */
53-
ignoreOrder?: boolean;
113+
/** If a key is not present in the new object, it will be ignored (by default the key is present and set to `undefined`). */
114+
ignoreRemovedKeys?: boolean;
54115

55116
/** Whether to perform a deep comparison of nested objects (does not apply to arrays). */
56117
recursive?: boolean;
118+
119+
/** Whether to strictly check the order of items in arrays (default false). */
120+
strictOrder?: boolean;
57121
},
58122
): Partial<O & N> {
59123
if (typeof oldObj !== 'object' || oldObj === null || typeof newObj !== 'object' || newObj === null) {
60-
return deepEqual(oldObj, newObj, !options?.ignoreOrder) ? (oldObj as any) : (newObj as any);
124+
return deepEqual(oldObj, newObj, options?.strictOrder) ? (oldObj as any) : (newObj as any);
61125
}
62126
const diff: Partial<O & N> = {};
63127
const oldKeys = new Set<string>(Object.keys(oldObj));
@@ -72,15 +136,16 @@ export function difference<O, N>(
72136
// recursive difference
73137
if (options?.recursive && typeof oldValue === 'object' && oldValue !== null) {
74138
if (typeof newValue !== 'object' || newValue === null) {
75-
(diff as any)[key] = newValue;
139+
if (newValue !== null || !options.ignoreRemovedKeys) (diff as any)[key] = newValue;
76140
continue;
77141
}
78142
(diff as any)[key] = difference(oldValue, newValue, options);
79143
continue;
80144
}
81145

82146
// flat difference
83-
if (!deepEqual(oldValue, newValue, !options?.ignoreOrder)) (diff as any)[key] = newValue;
147+
if (!deepEqual(oldValue, newValue, options?.strictOrder) && (!options?.ignoreRemovedKeys || newValue !== undefined))
148+
(diff as any)[key] = newValue;
84149
}
85150

86151
// iterate new object

0 commit comments

Comments
 (0)