Skip to content

Commit fba7c65

Browse files
authored
feat: set support merge (#441)
1 parent d319153 commit fba7c65

File tree

2 files changed

+138
-3
lines changed

2 files changed

+138
-3
lines changed

src/utils/set.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import get from './get';
22

3+
export type Path = (string | number)[];
4+
35
function internalSet<Entity = any, Output = Entity, Value = any>(
46
entity: Entity,
5-
paths: (string | number)[],
7+
paths: Path,
68
value: Value,
79
removeIfUndefined: boolean,
810
): Output {
@@ -49,3 +51,52 @@ export default function set<Entity = any, Output = Entity, Value = any>(
4951

5052
return internalSet(entity, paths, value, removeIfUndefined);
5153
}
54+
55+
function isObject(obj: any) {
56+
return (
57+
typeof obj === 'object' &&
58+
obj !== null &&
59+
Object.getPrototypeOf(obj) === Object.prototype
60+
);
61+
}
62+
63+
function createEmpty<T>(source: T) {
64+
return (Array.isArray(source) ? [] : {}) as T;
65+
}
66+
67+
/**
68+
* Merge objects which will create
69+
*/
70+
export function merge<T extends object>(...sources: T[]) {
71+
let clone = createEmpty(sources[0]);
72+
73+
const loopSet = new Set<object>();
74+
75+
sources.forEach(src => {
76+
function internalMerge(path: Path) {
77+
const value = get(src, path);
78+
79+
if (isObject(value) || Array.isArray(value)) {
80+
// Only add not loop obj
81+
if (!loopSet.has(value)) {
82+
loopSet.add(value);
83+
84+
// Init container if not exist
85+
if (!get(clone, path)) {
86+
clone = set(clone, path, createEmpty(value));
87+
}
88+
89+
Object.keys(value).forEach(key => {
90+
internalMerge([...path, key]);
91+
});
92+
}
93+
} else {
94+
clone = set(clone, path, value);
95+
}
96+
}
97+
98+
internalMerge([]);
99+
});
100+
101+
return clone;
102+
}

tests/utils.test.js

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import get from '../src/utils/get';
2-
import set from '../src/utils/set';
31
import pickAttrs from '../src/pickAttrs';
2+
import get from '../src/utils/get';
3+
import set, { merge } from '../src/utils/set';
44

55
describe('utils', () => {
66
it('get', () => {
@@ -96,6 +96,90 @@ describe('utils', () => {
9696
expect(longTgt).toEqual({ lv1: { lv2: { lv3: {} } } });
9797
expect('lv4' in longTgt.lv1.lv2.lv3).toBeFalsy();
9898
});
99+
100+
describe('merge', () => {
101+
it('basic', () => {
102+
const merged = merge({}, { a: 1 }, { b: 2 });
103+
104+
expect(merged).toEqual({
105+
a: 1,
106+
b: 2,
107+
});
108+
});
109+
110+
it('array', () => {
111+
const merged = merge([], [{ a: 1 }], [{ b: 2 }]);
112+
113+
expect(merged).toEqual([
114+
{
115+
a: 1,
116+
b: 2,
117+
},
118+
]);
119+
});
120+
121+
it('not cover', () => {
122+
const merged = merge(
123+
[],
124+
[{ a: { e: 8 }, b: 2 }],
125+
[{ a: { f: 9 }, c: 3 }],
126+
);
127+
128+
expect(merged).toEqual([
129+
{
130+
a: {
131+
e: 8,
132+
f: 9,
133+
},
134+
b: 2,
135+
c: 3,
136+
},
137+
]);
138+
});
139+
140+
it('DayObject', () => {
141+
const now = new Date();
142+
143+
const merged = merge(
144+
{ a: 123 },
145+
{ b: 234 },
146+
{
147+
now,
148+
},
149+
);
150+
151+
expect(merged).toEqual({
152+
a: 123,
153+
b: 234,
154+
now,
155+
});
156+
157+
expect(merged.now).toBe(now);
158+
});
159+
160+
it('empty object', () => {
161+
const merged = merge({}, { a: {} });
162+
expect(merged).toEqual({ a: {} });
163+
});
164+
165+
it('no dead-loop', () => {
166+
const looper = {
167+
a: 1,
168+
b: {
169+
c: 3,
170+
},
171+
};
172+
looper.b.looper = looper;
173+
174+
const merged = merge(looper);
175+
expect(merged).toEqual({
176+
a: 1,
177+
b: {
178+
c: 3,
179+
},
180+
});
181+
});
182+
});
99183
});
100184

101185
describe('pickAttrs', () => {

0 commit comments

Comments
 (0)