merge-anything 2.2.3 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -22
- package/dist/index.cjs.js +49 -22
- package/dist/index.esm.js +47 -23
- package/package.json +8 -8
- package/src/extensions.ts +9 -0
- package/src/index.ts +4 -77
- package/src/merge.ts +98 -0
- package/test/index.js +75 -1
- package/types/extensions.d.ts +1 -0
- package/types/index.d.ts +4 -15
- package/types/merge.d.ts +15 -0
package/README.md
CHANGED
|
@@ -66,6 +66,7 @@ const c = merge(a, b)
|
|
|
66
66
|
// However, be careful with JavaScript object references. See below: A note on JavaScript object references
|
|
67
67
|
|
|
68
68
|
// arrays get overwritten
|
|
69
|
+
// (for "concat" logic, see Extensions below)
|
|
69
70
|
merge({array: ['a']}, {array: ['b']}) // returns {array: ['b']}
|
|
70
71
|
|
|
71
72
|
// empty objects merge into objects
|
|
@@ -83,15 +84,54 @@ merge-anything properly keeps special objects intact like dates, regex, function
|
|
|
83
84
|
|
|
84
85
|
However, it's **very important** you understand how to work around JavaScript object references. Please be sure to read [a note on JavaScript object references](#a-note-on-javascript-object-references) down below.
|
|
85
86
|
|
|
86
|
-
##
|
|
87
|
+
## Extensions & custom rules
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
There might be times you need to tweak the logic when two things are merged, eg. you need arrays to be *concatenated* instead of *overwritten*. This is possible through an extension!
|
|
89
90
|
|
|
90
|
-
|
|
91
|
+
To keep the source code _as small as possible_ I opted for an extionsion system where you can import just the logic you need.
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
### Concat arrays extension
|
|
93
94
|
|
|
94
|
-
|
|
95
|
+
```js
|
|
96
|
+
import { merge, concatArrays } from 'merge-anything'
|
|
97
|
+
|
|
98
|
+
merge(
|
|
99
|
+
{extensions: [concatArrays]}, // pass your extensions like so
|
|
100
|
+
{array: ['a']},
|
|
101
|
+
{array: ['b']}
|
|
102
|
+
)
|
|
103
|
+
// returns {array: ['a', 'b']}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
All extensions get triggered at the top level and at every single nested prop. Let's look at two more examples to clarify this:
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
// top level:
|
|
110
|
+
merge(
|
|
111
|
+
{extensions: [concatArrays]},
|
|
112
|
+
['a'],
|
|
113
|
+
['b']
|
|
114
|
+
)
|
|
115
|
+
// returns ['a', 'b']
|
|
116
|
+
|
|
117
|
+
// nested props:
|
|
118
|
+
merge(
|
|
119
|
+
{extensions: [concatArrays]}, // pass your extensions like so
|
|
120
|
+
{nested: {prop: {array: ['a']}}},
|
|
121
|
+
{nested: {prop: {array: ['b']}}},
|
|
122
|
+
)
|
|
123
|
+
// returns {nested: {prop: {array: ['a', 'b']}}},
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Super simple!
|
|
127
|
+
|
|
128
|
+
### Making your own extension / custom rule
|
|
129
|
+
|
|
130
|
+
The `concatArrays` extension imported above is actually just a function that takes both values it's trying to merge, and returns a new value. An "extension" function is triggered at the top level and on any nested props that should be merged.
|
|
131
|
+
|
|
132
|
+
This means that merge-anything can be really powerful because every step of the way **you can define rules to extend the overwrite logic.** Let's look at the `concatArrays` function as an example to see how you can write your own custom rules:
|
|
133
|
+
|
|
134
|
+
You have to define a function that receives two parameters. The first is the original value and the second is the new value that's being merged onto the original. When concatenating arrays, you can simply check if the value is an array or not and concatenate if it is.
|
|
95
135
|
|
|
96
136
|
```js
|
|
97
137
|
function concatArrays (originVal, newVal) {
|
|
@@ -102,29 +142,29 @@ function concatArrays (originVal, newVal) {
|
|
|
102
142
|
return newVal // always return newVal as fallback!!
|
|
103
143
|
}
|
|
104
144
|
merge(
|
|
105
|
-
{extensions: [concatArrays]}, // pass your
|
|
145
|
+
{extensions: [concatArrays]}, // pass your custom functions like so
|
|
106
146
|
{array: ['a']},
|
|
107
147
|
{array: ['b']}
|
|
108
148
|
)
|
|
109
149
|
// results in {array: ['a', 'b']}
|
|
110
150
|
```
|
|
111
151
|
|
|
112
|
-
Please note that each extension-function receives an `originVal` and `newVal` and **has** to return the `newVal` on fallback no matter what
|
|
152
|
+
Please note that each extension-function receives an `originVal` and `newVal` and **has** to return the `newVal` on fallback no matter what. Otherwise there might be cases that the original value is overwritten with `undefined`.
|
|
113
153
|
|
|
114
154
|
## A note on JavaScript object references
|
|
115
155
|
|
|
116
156
|
Be careful for JavaScript object reference. Any property that's nested will be reactive and linked between the original and the merged objects! Down below we'll show how to prevent this.
|
|
117
157
|
|
|
118
158
|
```js
|
|
119
|
-
const original = {airport: {airplane: '🛫'}}
|
|
120
|
-
const
|
|
121
|
-
const merged = merge(original,
|
|
159
|
+
const original = {airport: {airplane: 'dep. 🛫'}}
|
|
160
|
+
const extraInfo = {airport: {location: 'Brussels'}}
|
|
161
|
+
const merged = merge(original, extraInfo)
|
|
122
162
|
|
|
123
163
|
// we change the airplane from departuring 🛫 to landing 🛬
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
original
|
|
127
|
-
|
|
164
|
+
merged.airport.airplane = 'lan. 🛬'
|
|
165
|
+
(merged.airport.airplane === 'lan. 🛬') // true
|
|
166
|
+
// However, `original` was also modified!
|
|
167
|
+
(original.airport.airplane === 'lan. 🛬') // true
|
|
128
168
|
```
|
|
129
169
|
|
|
130
170
|
The key rule to remember is:
|
|
@@ -134,21 +174,23 @@ The key rule to remember is:
|
|
|
134
174
|
However, **there is a really easy solution**. We can just copy the merge result to get rid of any reactivity. For this we can use the [copy-anything](https://github.com/mesqueeb/copy-anything) library. This library also makes sure that _special class instances do not break_, so you can use it without fear of breaking stuff!
|
|
135
175
|
|
|
136
176
|
See below how we integrate 'copy-anything':
|
|
177
|
+
|
|
137
178
|
```js
|
|
138
179
|
import copy from 'copy-anything'
|
|
139
180
|
|
|
140
|
-
const original = {airport: {airplane: '🛫'}}
|
|
141
|
-
const
|
|
142
|
-
const merged = merge(original,
|
|
143
|
-
const mergedNotReactive = copy(merged)
|
|
181
|
+
const original = {airport: {airplane: 'dep. 🛫'}}
|
|
182
|
+
const extraInfo = {airport: {location: 'Brussels'}}
|
|
183
|
+
const merged = copy(merge(original, extraInfo))
|
|
144
184
|
|
|
145
185
|
// we change the airplane from departuring 🛫 to landing 🛬
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
original
|
|
149
|
-
|
|
186
|
+
merged.airport.airplane = 'lan. 🛬'
|
|
187
|
+
(merged.airport.airplane === 'lan. 🛬') // true
|
|
188
|
+
// `original` won't be modified!
|
|
189
|
+
(original.airport.airplane === 'lan. 🛬') // true
|
|
150
190
|
```
|
|
151
191
|
|
|
192
|
+
You can then play around where you want to place the `copy()` function.
|
|
193
|
+
|
|
152
194
|
## Source code
|
|
153
195
|
|
|
154
196
|
It is literally just going through an object recursively and assigning the values to a new object like below. However, it's wrapped to allow extra params etc. The code below is the basic integration, that will make you understand the basics how it works.
|
package/dist/index.cjs.js
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
3
5
|
var isWhat = require('is-what');
|
|
4
6
|
|
|
7
|
+
function assignProp(carry, key, newVal, originalObject) {
|
|
8
|
+
var propType = originalObject.propertyIsEnumerable(key)
|
|
9
|
+
? 'enumerable'
|
|
10
|
+
: 'nonenumerable';
|
|
11
|
+
if (propType === 'enumerable')
|
|
12
|
+
carry[key] = newVal;
|
|
13
|
+
if (propType === 'nonenumerable') {
|
|
14
|
+
Object.defineProperty(carry, key, {
|
|
15
|
+
value: newVal,
|
|
16
|
+
enumerable: false,
|
|
17
|
+
writable: true,
|
|
18
|
+
configurable: true
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
5
22
|
function mergeRecursively(origin, newComer, extensions) {
|
|
6
23
|
// work directly on newComer if its not an object
|
|
7
24
|
if (!isWhat.isPlainObject(newComer)) {
|
|
@@ -14,21 +31,27 @@ function mergeRecursively(origin, newComer, extensions) {
|
|
|
14
31
|
return newComer;
|
|
15
32
|
}
|
|
16
33
|
// define newObject to merge all values upon
|
|
17
|
-
var newObject =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
34
|
+
var newObject = {};
|
|
35
|
+
if (isWhat.isPlainObject(origin)) {
|
|
36
|
+
var props_1 = Object.getOwnPropertyNames(origin);
|
|
37
|
+
var symbols_1 = Object.getOwnPropertySymbols(origin);
|
|
38
|
+
newObject = props_1.concat(symbols_1).reduce(function (carry, key) {
|
|
21
39
|
// @ts-ignore
|
|
22
|
-
|
|
23
|
-
|
|
40
|
+
var targetVal = origin[key];
|
|
41
|
+
if ((!isWhat.isSymbol(key) && !Object.getOwnPropertyNames(newComer).includes(key)) ||
|
|
42
|
+
(isWhat.isSymbol(key) && !Object.getOwnPropertySymbols(newComer).includes(key))) {
|
|
43
|
+
assignProp(carry, key, targetVal, origin);
|
|
44
|
+
}
|
|
24
45
|
return carry;
|
|
25
|
-
}, {})
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
46
|
+
}, {});
|
|
47
|
+
}
|
|
48
|
+
var props = Object.getOwnPropertyNames(newComer);
|
|
49
|
+
var symbols = Object.getOwnPropertySymbols(newComer);
|
|
50
|
+
var result = props.concat(symbols).reduce(function (carry, key) {
|
|
29
51
|
// re-define the origin and newComer as targetVal and newVal
|
|
30
52
|
var newVal = newComer[key];
|
|
31
53
|
var targetVal = (isWhat.isPlainObject(origin))
|
|
54
|
+
// @ts-ignore
|
|
32
55
|
? origin[key]
|
|
33
56
|
: undefined;
|
|
34
57
|
// extend merge rules
|
|
@@ -37,20 +60,14 @@ function mergeRecursively(origin, newComer, extensions) {
|
|
|
37
60
|
newVal = extend(targetVal, newVal);
|
|
38
61
|
});
|
|
39
62
|
}
|
|
40
|
-
// early return when targetVal === undefined
|
|
41
|
-
if (targetVal === undefined) {
|
|
42
|
-
carry[key] = newVal;
|
|
43
|
-
return carry;
|
|
44
|
-
}
|
|
45
63
|
// When newVal is an object do the merge recursively
|
|
46
|
-
if (isWhat.isPlainObject(newVal)) {
|
|
47
|
-
|
|
48
|
-
return carry;
|
|
64
|
+
if (targetVal !== undefined && isWhat.isPlainObject(newVal)) {
|
|
65
|
+
newVal = mergeRecursively(targetVal, newVal, extensions);
|
|
49
66
|
}
|
|
50
|
-
|
|
51
|
-
carry[key] = newVal;
|
|
67
|
+
assignProp(carry, key, newVal, newComer);
|
|
52
68
|
return carry;
|
|
53
69
|
}, newObject);
|
|
70
|
+
return result;
|
|
54
71
|
}
|
|
55
72
|
/**
|
|
56
73
|
* Merge anything recursively.
|
|
@@ -61,7 +78,7 @@ function mergeRecursively(origin, newComer, extensions) {
|
|
|
61
78
|
* @param {...any[]} newComers
|
|
62
79
|
* @returns the result
|
|
63
80
|
*/
|
|
64
|
-
function
|
|
81
|
+
function merge(origin) {
|
|
65
82
|
var newComers = [];
|
|
66
83
|
for (var _i = 1; _i < arguments.length; _i++) {
|
|
67
84
|
newComers[_i - 1] = arguments[_i];
|
|
@@ -77,4 +94,14 @@ function index (origin) {
|
|
|
77
94
|
}, base);
|
|
78
95
|
}
|
|
79
96
|
|
|
80
|
-
|
|
97
|
+
function concatArrays(originVal, newVal) {
|
|
98
|
+
if (isWhat.isArray(originVal) && isWhat.isArray(newVal)) {
|
|
99
|
+
// concat logic
|
|
100
|
+
return originVal.concat(newVal);
|
|
101
|
+
}
|
|
102
|
+
return newVal; // always return newVal as fallback!!
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
exports.merge = merge;
|
|
106
|
+
exports.concatArrays = concatArrays;
|
|
107
|
+
exports.default = merge;
|
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
|
-
import { isArray, isPlainObject } from 'is-what';
|
|
1
|
+
import { isArray, isPlainObject, isSymbol } from 'is-what';
|
|
2
2
|
|
|
3
|
+
function assignProp(carry, key, newVal, originalObject) {
|
|
4
|
+
var propType = originalObject.propertyIsEnumerable(key)
|
|
5
|
+
? 'enumerable'
|
|
6
|
+
: 'nonenumerable';
|
|
7
|
+
if (propType === 'enumerable')
|
|
8
|
+
carry[key] = newVal;
|
|
9
|
+
if (propType === 'nonenumerable') {
|
|
10
|
+
Object.defineProperty(carry, key, {
|
|
11
|
+
value: newVal,
|
|
12
|
+
enumerable: false,
|
|
13
|
+
writable: true,
|
|
14
|
+
configurable: true
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
3
18
|
function mergeRecursively(origin, newComer, extensions) {
|
|
4
19
|
// work directly on newComer if its not an object
|
|
5
20
|
if (!isPlainObject(newComer)) {
|
|
@@ -12,21 +27,27 @@ function mergeRecursively(origin, newComer, extensions) {
|
|
|
12
27
|
return newComer;
|
|
13
28
|
}
|
|
14
29
|
// define newObject to merge all values upon
|
|
15
|
-
var newObject =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
var newObject = {};
|
|
31
|
+
if (isPlainObject(origin)) {
|
|
32
|
+
var props_1 = Object.getOwnPropertyNames(origin);
|
|
33
|
+
var symbols_1 = Object.getOwnPropertySymbols(origin);
|
|
34
|
+
newObject = props_1.concat(symbols_1).reduce(function (carry, key) {
|
|
19
35
|
// @ts-ignore
|
|
20
|
-
|
|
21
|
-
|
|
36
|
+
var targetVal = origin[key];
|
|
37
|
+
if ((!isSymbol(key) && !Object.getOwnPropertyNames(newComer).includes(key)) ||
|
|
38
|
+
(isSymbol(key) && !Object.getOwnPropertySymbols(newComer).includes(key))) {
|
|
39
|
+
assignProp(carry, key, targetVal, origin);
|
|
40
|
+
}
|
|
22
41
|
return carry;
|
|
23
|
-
}, {})
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
42
|
+
}, {});
|
|
43
|
+
}
|
|
44
|
+
var props = Object.getOwnPropertyNames(newComer);
|
|
45
|
+
var symbols = Object.getOwnPropertySymbols(newComer);
|
|
46
|
+
var result = props.concat(symbols).reduce(function (carry, key) {
|
|
27
47
|
// re-define the origin and newComer as targetVal and newVal
|
|
28
48
|
var newVal = newComer[key];
|
|
29
49
|
var targetVal = (isPlainObject(origin))
|
|
50
|
+
// @ts-ignore
|
|
30
51
|
? origin[key]
|
|
31
52
|
: undefined;
|
|
32
53
|
// extend merge rules
|
|
@@ -35,20 +56,14 @@ function mergeRecursively(origin, newComer, extensions) {
|
|
|
35
56
|
newVal = extend(targetVal, newVal);
|
|
36
57
|
});
|
|
37
58
|
}
|
|
38
|
-
// early return when targetVal === undefined
|
|
39
|
-
if (targetVal === undefined) {
|
|
40
|
-
carry[key] = newVal;
|
|
41
|
-
return carry;
|
|
42
|
-
}
|
|
43
59
|
// When newVal is an object do the merge recursively
|
|
44
|
-
if (isPlainObject(newVal)) {
|
|
45
|
-
|
|
46
|
-
return carry;
|
|
60
|
+
if (targetVal !== undefined && isPlainObject(newVal)) {
|
|
61
|
+
newVal = mergeRecursively(targetVal, newVal, extensions);
|
|
47
62
|
}
|
|
48
|
-
|
|
49
|
-
carry[key] = newVal;
|
|
63
|
+
assignProp(carry, key, newVal, newComer);
|
|
50
64
|
return carry;
|
|
51
65
|
}, newObject);
|
|
66
|
+
return result;
|
|
52
67
|
}
|
|
53
68
|
/**
|
|
54
69
|
* Merge anything recursively.
|
|
@@ -59,7 +74,7 @@ function mergeRecursively(origin, newComer, extensions) {
|
|
|
59
74
|
* @param {...any[]} newComers
|
|
60
75
|
* @returns the result
|
|
61
76
|
*/
|
|
62
|
-
function
|
|
77
|
+
function merge(origin) {
|
|
63
78
|
var newComers = [];
|
|
64
79
|
for (var _i = 1; _i < arguments.length; _i++) {
|
|
65
80
|
newComers[_i - 1] = arguments[_i];
|
|
@@ -75,4 +90,13 @@ function index (origin) {
|
|
|
75
90
|
}, base);
|
|
76
91
|
}
|
|
77
92
|
|
|
78
|
-
|
|
93
|
+
function concatArrays(originVal, newVal) {
|
|
94
|
+
if (isArray(originVal) && isArray(newVal)) {
|
|
95
|
+
// concat logic
|
|
96
|
+
return originVal.concat(newVal);
|
|
97
|
+
}
|
|
98
|
+
return newVal; // always return newVal as fallback!!
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default merge;
|
|
102
|
+
export { merge, concatArrays };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "merge-anything",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Merge objects & other types recursively. A simple & small integration.",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -44,15 +44,15 @@
|
|
|
44
44
|
"homepage": "https://github.com/mesqueeb/merge-anything#readme",
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@ava/babel-preset-stage-4": "^2.0.0",
|
|
47
|
-
"@babel/plugin-proposal-object-rest-spread": "^7.
|
|
48
|
-
"@babel/preset-env": "^7.
|
|
49
|
-
"ava": "1.
|
|
50
|
-
"copy-anything": "^1.
|
|
47
|
+
"@babel/plugin-proposal-object-rest-spread": "^7.5.4",
|
|
48
|
+
"@babel/preset-env": "^7.5.4",
|
|
49
|
+
"ava": "^1.4.1",
|
|
50
|
+
"copy-anything": "^1.2.4",
|
|
51
51
|
"rollup": "^0.65.2",
|
|
52
|
-
"rollup-plugin-typescript2": "^0.
|
|
53
|
-
"typescript": "^3.
|
|
52
|
+
"rollup-plugin-typescript2": "^0.21.2",
|
|
53
|
+
"typescript": "^3.5.3"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"is-what": "^3.
|
|
56
|
+
"is-what": "^3.2.4"
|
|
57
57
|
}
|
|
58
58
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,78 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import merge from './merge'
|
|
2
|
+
import { concatArrays } from './extensions'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
interface IConfig {
|
|
6
|
-
extensions: Extension[]
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function mergeRecursively(origin: any, newComer: any, extensions: Extension[]) {
|
|
10
|
-
// work directly on newComer if its not an object
|
|
11
|
-
if (!isPlainObject(newComer)) {
|
|
12
|
-
// extend merge rules
|
|
13
|
-
if (extensions && isArray(extensions)) {
|
|
14
|
-
extensions.forEach(extend => {
|
|
15
|
-
newComer = extend(origin, newComer)
|
|
16
|
-
})
|
|
17
|
-
}
|
|
18
|
-
return newComer
|
|
19
|
-
}
|
|
20
|
-
// define newObject to merge all values upon
|
|
21
|
-
const newObject = (isPlainObject(origin))
|
|
22
|
-
? Object.keys(origin)
|
|
23
|
-
.reduce((carry, key) => {
|
|
24
|
-
const targetVal = origin[key]
|
|
25
|
-
// @ts-ignore
|
|
26
|
-
if (!Object.keys(newComer).includes(key)) carry[key] = targetVal
|
|
27
|
-
return carry
|
|
28
|
-
}, {})
|
|
29
|
-
: {}
|
|
30
|
-
return Object.keys(newComer)
|
|
31
|
-
.reduce((carry, key) => {
|
|
32
|
-
// re-define the origin and newComer as targetVal and newVal
|
|
33
|
-
let newVal = newComer[key]
|
|
34
|
-
const targetVal = (isPlainObject(origin))
|
|
35
|
-
? origin[key]
|
|
36
|
-
: undefined
|
|
37
|
-
// extend merge rules
|
|
38
|
-
if (extensions && isArray(extensions)) {
|
|
39
|
-
extensions.forEach(extend => {
|
|
40
|
-
newVal = extend(targetVal, newVal)
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
// early return when targetVal === undefined
|
|
44
|
-
if (targetVal === undefined) {
|
|
45
|
-
carry[key] = newVal
|
|
46
|
-
return carry
|
|
47
|
-
}
|
|
48
|
-
// When newVal is an object do the merge recursively
|
|
49
|
-
if (isPlainObject(newVal)) {
|
|
50
|
-
carry[key] = mergeRecursively(targetVal, newVal, extensions)
|
|
51
|
-
return carry
|
|
52
|
-
}
|
|
53
|
-
// all the rest
|
|
54
|
-
carry[key] = newVal
|
|
55
|
-
return carry
|
|
56
|
-
}, newObject)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Merge anything recursively.
|
|
61
|
-
* Objects get merged, special objects (classes etc.) are re-assigned "as is".
|
|
62
|
-
* Basic types overwrite objects or other basic types.
|
|
63
|
-
*
|
|
64
|
-
* @param {(IConfig | any)} origin
|
|
65
|
-
* @param {...any[]} newComers
|
|
66
|
-
* @returns the result
|
|
67
|
-
*/
|
|
68
|
-
export default function (origin: IConfig | any, ...newComers: any[]) {
|
|
69
|
-
let extensions = null
|
|
70
|
-
let base = origin
|
|
71
|
-
if (isPlainObject(origin) && origin.extensions && Object.keys(origin).length === 1) {
|
|
72
|
-
base = {}
|
|
73
|
-
extensions = origin.extensions
|
|
74
|
-
}
|
|
75
|
-
return newComers.reduce((result, newComer) => {
|
|
76
|
-
return mergeRecursively(result, newComer, extensions)
|
|
77
|
-
}, base)
|
|
78
|
-
}
|
|
4
|
+
export { merge, concatArrays }
|
|
5
|
+
export default merge
|
package/src/merge.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { isArray, isPlainObject, isSymbol } from 'is-what'
|
|
2
|
+
|
|
3
|
+
type Extension = (param1: any, param2: any) => any
|
|
4
|
+
|
|
5
|
+
interface IConfig {
|
|
6
|
+
extensions: Extension[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function assignProp (carry, key, newVal, originalObject) {
|
|
10
|
+
const propType = originalObject.propertyIsEnumerable(key)
|
|
11
|
+
? 'enumerable'
|
|
12
|
+
: 'nonenumerable'
|
|
13
|
+
if (propType === 'enumerable') carry[key] = newVal
|
|
14
|
+
if (propType === 'nonenumerable') {
|
|
15
|
+
Object.defineProperty(carry, key, {
|
|
16
|
+
value: newVal,
|
|
17
|
+
enumerable: false,
|
|
18
|
+
writable: true,
|
|
19
|
+
configurable: true
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function mergeRecursively(origin: any, newComer: any, extensions: Extension[]) {
|
|
25
|
+
// work directly on newComer if its not an object
|
|
26
|
+
if (!isPlainObject(newComer)) {
|
|
27
|
+
// extend merge rules
|
|
28
|
+
if (extensions && isArray(extensions)) {
|
|
29
|
+
extensions.forEach(extend => {
|
|
30
|
+
newComer = extend(origin, newComer)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
return newComer
|
|
34
|
+
}
|
|
35
|
+
// define newObject to merge all values upon
|
|
36
|
+
let newObject = {}
|
|
37
|
+
if (isPlainObject(origin)) {
|
|
38
|
+
const props = Object.getOwnPropertyNames(origin)
|
|
39
|
+
const symbols = Object.getOwnPropertySymbols(origin)
|
|
40
|
+
newObject = [...props, ...symbols]
|
|
41
|
+
.reduce((carry, key) => {
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
const targetVal = origin[key]
|
|
44
|
+
if (
|
|
45
|
+
(!isSymbol(key) && !Object.getOwnPropertyNames(newComer).includes(key)) ||
|
|
46
|
+
(isSymbol(key) && !Object.getOwnPropertySymbols(newComer).includes(key))
|
|
47
|
+
) {
|
|
48
|
+
assignProp(carry, key, targetVal, origin)
|
|
49
|
+
}
|
|
50
|
+
return carry
|
|
51
|
+
}, {})
|
|
52
|
+
}
|
|
53
|
+
const props = Object.getOwnPropertyNames(newComer)
|
|
54
|
+
const symbols = Object.getOwnPropertySymbols(newComer)
|
|
55
|
+
let result = [...props, ...symbols]
|
|
56
|
+
.reduce((carry, key) => {
|
|
57
|
+
// re-define the origin and newComer as targetVal and newVal
|
|
58
|
+
let newVal = newComer[key]
|
|
59
|
+
const targetVal = (isPlainObject(origin))
|
|
60
|
+
// @ts-ignore
|
|
61
|
+
? origin[key]
|
|
62
|
+
: undefined
|
|
63
|
+
// extend merge rules
|
|
64
|
+
if (extensions && isArray(extensions)) {
|
|
65
|
+
extensions.forEach(extend => {
|
|
66
|
+
newVal = extend(targetVal, newVal)
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
// When newVal is an object do the merge recursively
|
|
70
|
+
if (targetVal !== undefined && isPlainObject(newVal)) {
|
|
71
|
+
newVal = mergeRecursively(targetVal, newVal, extensions)
|
|
72
|
+
}
|
|
73
|
+
assignProp(carry, key, newVal, newComer)
|
|
74
|
+
return carry
|
|
75
|
+
}, newObject)
|
|
76
|
+
return result
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Merge anything recursively.
|
|
81
|
+
* Objects get merged, special objects (classes etc.) are re-assigned "as is".
|
|
82
|
+
* Basic types overwrite objects or other basic types.
|
|
83
|
+
*
|
|
84
|
+
* @param {(IConfig | any)} origin
|
|
85
|
+
* @param {...any[]} newComers
|
|
86
|
+
* @returns the result
|
|
87
|
+
*/
|
|
88
|
+
export default function merge (origin: IConfig | any, ...newComers: any[]) {
|
|
89
|
+
let extensions = null
|
|
90
|
+
let base = origin
|
|
91
|
+
if (isPlainObject(origin) && origin.extensions && Object.keys(origin).length === 1) {
|
|
92
|
+
base = {}
|
|
93
|
+
extensions = origin.extensions
|
|
94
|
+
}
|
|
95
|
+
return newComers.reduce((result, newComer) => {
|
|
96
|
+
return mergeRecursively(result, newComer, extensions)
|
|
97
|
+
}, base)
|
|
98
|
+
}
|
package/test/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import test from 'ava'
|
|
2
2
|
import merge from '../dist/index.cjs'
|
|
3
|
+
import { concatArrays } from '../dist/index.cjs'
|
|
3
4
|
import copy from 'copy-anything'
|
|
4
5
|
import { isDate, isFunction, isString, isArray, isObject } from 'is-what'
|
|
5
6
|
|
|
@@ -160,7 +161,7 @@ test('Extend conversion', t => {
|
|
|
160
161
|
})
|
|
161
162
|
test('Extend concat arrays', t => {
|
|
162
163
|
let res, origin, target
|
|
163
|
-
function
|
|
164
|
+
function concatArr (originVal, targetVal) {
|
|
164
165
|
if (isArray(originVal) && isArray(targetVal)) {
|
|
165
166
|
return originVal.concat(targetVal)
|
|
166
167
|
}
|
|
@@ -174,6 +175,22 @@ test('Extend concat arrays', t => {
|
|
|
174
175
|
someArray: ['b'],
|
|
175
176
|
a: {b: {c: ['y']}}
|
|
176
177
|
}
|
|
178
|
+
res = merge({extensions: [concatArr]}, origin, target)
|
|
179
|
+
t.deepEqual(res, {someArray: ['a', 'b'], a: {b: {c: ['x', 'y']}}})
|
|
180
|
+
// also works on base lvl
|
|
181
|
+
res = merge({extensions: [concatArr]}, ['a'], ['b'])
|
|
182
|
+
t.deepEqual(res, ['a', 'b'])
|
|
183
|
+
})
|
|
184
|
+
test('import concat array extension', t => {
|
|
185
|
+
let res, origin, target
|
|
186
|
+
origin = {
|
|
187
|
+
someArray: ['a'],
|
|
188
|
+
a: {b: {c: ['x']}}
|
|
189
|
+
}
|
|
190
|
+
target = {
|
|
191
|
+
someArray: ['b'],
|
|
192
|
+
a: {b: {c: ['y']}}
|
|
193
|
+
}
|
|
177
194
|
res = merge({extensions: [concatArrays]}, origin, target)
|
|
178
195
|
t.deepEqual(res, {someArray: ['a', 'b'], a: {b: {c: ['x', 'y']}}})
|
|
179
196
|
// also works on base lvl
|
|
@@ -353,3 +370,60 @@ test('works with unlimited depth', t => {
|
|
|
353
370
|
t.deepEqual(t3, {t3: 'new'})
|
|
354
371
|
t.deepEqual(t4, {t4: 'new', t3: {}})
|
|
355
372
|
})
|
|
373
|
+
|
|
374
|
+
test('symbols as keys', t => {
|
|
375
|
+
let res, x, y
|
|
376
|
+
const mySymbol = Symbol('mySymbol')
|
|
377
|
+
x = { value: 42, [mySymbol]: 'hello' }
|
|
378
|
+
y = { other: 33 }
|
|
379
|
+
res = merge(x, y)
|
|
380
|
+
t.is(res.value, 42)
|
|
381
|
+
t.is(res.other, 33)
|
|
382
|
+
t.is(res[mySymbol], 'hello')
|
|
383
|
+
x = { value: 42 }
|
|
384
|
+
y = { other: 33, [mySymbol]: 'hello' }
|
|
385
|
+
res = merge(x, y)
|
|
386
|
+
t.is(res.value, 42)
|
|
387
|
+
t.is(res.other, 33)
|
|
388
|
+
t.is(res[mySymbol], 'hello')
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
test('nonenumerable keys', t => {
|
|
392
|
+
let x, y, res
|
|
393
|
+
const mySymbol = Symbol('mySymbol')
|
|
394
|
+
x = { value: 42 }
|
|
395
|
+
y = { other: 33 }
|
|
396
|
+
Object.defineProperty(x, 'xid', {
|
|
397
|
+
value: 1,
|
|
398
|
+
writable: true,
|
|
399
|
+
enumerable: false,
|
|
400
|
+
configurable: true
|
|
401
|
+
})
|
|
402
|
+
Object.defineProperty(x, mySymbol, {
|
|
403
|
+
value: 'original',
|
|
404
|
+
writable: true,
|
|
405
|
+
enumerable: false,
|
|
406
|
+
configurable: true
|
|
407
|
+
})
|
|
408
|
+
Object.defineProperty(y, 'yid', {
|
|
409
|
+
value: 2,
|
|
410
|
+
writable: true,
|
|
411
|
+
enumerable: false,
|
|
412
|
+
configurable: true
|
|
413
|
+
})
|
|
414
|
+
Object.defineProperty(y, mySymbol, {
|
|
415
|
+
value: 'new',
|
|
416
|
+
writable: true,
|
|
417
|
+
enumerable: false,
|
|
418
|
+
configurable: true
|
|
419
|
+
})
|
|
420
|
+
res = merge(x, y)
|
|
421
|
+
t.is(res.value, 42)
|
|
422
|
+
t.is(res.other, 33)
|
|
423
|
+
t.is(res.xid, 1)
|
|
424
|
+
t.is(res.yid, 2)
|
|
425
|
+
t.is(res[mySymbol], 'new')
|
|
426
|
+
t.is(Object.keys(res).length, 2)
|
|
427
|
+
t.true(Object.keys(res).includes('value'))
|
|
428
|
+
t.true(Object.keys(res).includes('other'))
|
|
429
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function concatArrays(originVal: any, newVal: any): any;
|
package/types/index.d.ts
CHANGED
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Merge anything recursively.
|
|
7
|
-
* Objects get merged, special objects (classes etc.) are re-assigned "as is".
|
|
8
|
-
* Basic types overwrite objects or other basic types.
|
|
9
|
-
*
|
|
10
|
-
* @param {(IConfig | any)} origin
|
|
11
|
-
* @param {...any[]} newComers
|
|
12
|
-
* @returns the result
|
|
13
|
-
*/
|
|
14
|
-
export default function (origin: IConfig | any, ...newComers: any[]): any;
|
|
15
|
-
export {};
|
|
1
|
+
import merge from './merge';
|
|
2
|
+
import { concatArrays } from './extensions';
|
|
3
|
+
export { merge, concatArrays };
|
|
4
|
+
export default merge;
|
package/types/merge.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare type Extension = (param1: any, param2: any) => any;
|
|
2
|
+
interface IConfig {
|
|
3
|
+
extensions: Extension[];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Merge anything recursively.
|
|
7
|
+
* Objects get merged, special objects (classes etc.) are re-assigned "as is".
|
|
8
|
+
* Basic types overwrite objects or other basic types.
|
|
9
|
+
*
|
|
10
|
+
* @param {(IConfig | any)} origin
|
|
11
|
+
* @param {...any[]} newComers
|
|
12
|
+
* @returns the result
|
|
13
|
+
*/
|
|
14
|
+
export default function merge(origin: IConfig | any, ...newComers: any[]): any;
|
|
15
|
+
export {};
|