Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81158 views
1
/**
2
* Copyright 2013-2014, Facebook, Inc.
3
* All rights reserved.
4
*
5
* This source code is licensed under the BSD-style license found in the
6
* LICENSE file in the root directory of this source tree. An additional grant
7
* of patent rights can be found in the PATENTS file in the same directory.
8
*
9
* @providesModule mergeDeepInto
10
*/
11
12
// Empty blocks improve readability so disable that warning
13
// jshint -W035
14
15
"use strict";
16
17
var invariant = require('invariant');
18
var mergeHelpers = require('mergeHelpers');
19
20
var ArrayStrategies = mergeHelpers.ArrayStrategies;
21
var checkArrayStrategy = mergeHelpers.checkArrayStrategy;
22
var checkMergeArrayArgs = mergeHelpers.checkMergeArrayArgs;
23
var checkMergeLevel = mergeHelpers.checkMergeLevel;
24
var checkMergeObjectArgs = mergeHelpers.checkMergeObjectArgs;
25
var isTerminal = mergeHelpers.isTerminal;
26
var normalizeMergeArg = mergeHelpers.normalizeMergeArg;
27
28
/**
29
* Every deep merge function must handle merging in each of the following cases
30
* at every level. We may refer to letters below in implementations. For each
31
* case listed, the "Result" listed describes the *value* of the result, but
32
* does not specify anything about the memory graph of the result. In other
33
* words the Results listed below will be the same for `merge` and `mergeInto`
34
* but `merge` may have different guarantees about mutation/cloning. In the
35
* table, "Object" refers to a non- Array, non-terminal object. One result is
36
* undefined and requires that the caller specify the resolution policy (only
37
* when trying to merge two arrays).
38
*
39
* Scenario Result
40
* --------------------------------------------------------------
41
* [A]: (terminal, terminal) right terminal
42
* [B]: (terminal, Array) right Array
43
* [C]: (terminal, Object) right Object
44
* [D]: (terminal, not present) left terminal
45
* [E]: (Array, terminal) right terminal
46
* [F]: (Array, Array) UNDEFINED - MUST SPECIFY POLICY
47
* [G]: (Array, Object) right Object
48
* [H]: (Array, not present) left Array
49
* [I]: (Object, terminal) right terminal
50
* [J]: (Object, Array) right Array
51
* [K]: (Object, Object) merge of left and right Objects
52
* [L]: (Object, not present) left Object
53
* [M]: (not present, terminal) right terminal
54
* [N]: (not present, Array) right Array
55
* [O]: (not present, Object) right Object
56
*
57
* All merge functions are only expected to reason about "own" properties and,
58
* any prototypical properties should have no effect on the result. At the first
59
* level of recursion in deep merges, we may choose to normalize arguments
60
* (convert undefined to empty objects etc.) The above chart does not describe
61
* the result of those top level operations which behave specially due to the
62
* normalization.
63
*/
64
65
/**
66
* Deep merge implementation for non-Array, non-terminal Objects.
67
*
68
* @param {!Object} one non-Array, non-terminal Object to be mutated deeply.
69
* @param {!Object} two non-Array, non-terminal taking precedence over `one`.
70
* @param {?Enum=} arrayStrategy one of `arrayStrategies`.
71
* @param {!number} level The level of recursion.
72
*/
73
var mergeDeepIntoObjects = function(one, two, arrayStrategy, level) {
74
checkMergeObjectArgs(one, two);
75
checkMergeLevel(level);
76
var twoKeys = two ? Object.keys(two) : [];
77
for (var i = 0; i < twoKeys.length; i++) {
78
var twoKey = twoKeys[i];
79
mergeSingleFieldDeep(one, two, twoKey, arrayStrategy, level);
80
}
81
};
82
83
/**
84
* Deep merge implementation for Arrays.
85
*
86
* @param {!Array} one Array to deep merge.
87
* @param {!Array} two Array to deep merge "into" `one`.
88
* @param {?Enum=} arrayStrategy one of `arrayStrategies`.
89
* @param {!number} level Level of recursion.
90
*/
91
var mergeDeepIntoArrays = function(one, two, arrayStrategy, level) {
92
checkMergeArrayArgs(one, two);
93
checkMergeLevel(level);
94
95
var maxLen = Math.max(one.length, two.length);
96
for (var i = 0; i < maxLen; i++) {
97
mergeSingleFieldDeep(one, two, i, arrayStrategy, level);
98
}
99
};
100
101
/**
102
* Given two truthy containers `one` and `two`, and a `key`, performs a deep
103
* merge on a logical field.
104
*
105
* @param {!Array|Object} one Container to merge into.
106
* @param {!Array|Object} two Container to merge from.
107
* @param {!string} key Key of field that should be merged.
108
* @param {lEnum=} arrayStrategy One of `arrayStrategies`.
109
* @param {!number} level Current level of recursion.
110
*/
111
var mergeSingleFieldDeep = function(one, two, key, arrayStrategy, level) {
112
var twoVal = two[key];
113
var twoValIsPresent = two.hasOwnProperty(key);
114
var twoValIsTerminal = twoValIsPresent && isTerminal(twoVal);
115
var twoValIsArray = twoValIsPresent && Array.isArray(twoVal);
116
var twoValIsProperObject =
117
twoValIsPresent && !twoValIsArray && !twoValIsArray;
118
119
var oneVal = one[key];
120
var oneValIsPresent = one.hasOwnProperty(key);
121
var oneValIsTerminal = oneValIsPresent && isTerminal(oneVal);
122
var oneValIsArray = oneValIsPresent && Array.isArray(oneVal);
123
var oneValIsProperObject =
124
oneValIsPresent && !oneValIsArray && !oneValIsArray;
125
126
if (oneValIsTerminal) {
127
if (twoValIsTerminal) { // [A]
128
one[key] = twoVal;
129
} else if (twoValIsArray) { // [B]
130
one[key] = [];
131
mergeDeepIntoArrays(one[key], twoVal, arrayStrategy, level + 1);
132
} else if (twoValIsProperObject) { // [C]
133
one[key] = {};
134
mergeDeepIntoObjects(one[key], twoVal, arrayStrategy, level + 1);
135
} else if (!twoValIsPresent) { // [D]
136
one[key] = oneVal;
137
}
138
} else if (oneValIsArray) {
139
if (twoValIsTerminal) { // [E]
140
one[key] = twoVal;
141
} else if (twoValIsArray) { // [F]
142
invariant(
143
ArrayStrategies[arrayStrategy],
144
'mergeDeepInto(...): Attempted to merge two arrays, but a valid ' +
145
'ArrayStrategy was not specified.'
146
);
147
// Else: At this point, the only other valid option is `IndexByIndex`
148
if (arrayStrategy === ArrayStrategies.Clobber) {
149
oneVal.length = 0;
150
}
151
mergeDeepIntoArrays(oneVal, twoVal, arrayStrategy, level + 1);
152
} else if (twoValIsProperObject) { // [G]
153
one[key] = {};
154
mergeDeepIntoObjects(one[key], twoVal, arrayStrategy, level + 1);
155
} else if (!twoValIsPresent) { // [H]
156
// Leave the left Array alone
157
}
158
} else if (oneValIsProperObject) {
159
if (twoValIsTerminal) { // [I]
160
one[key] = twoVal;
161
} else if (twoValIsArray) { // [J]
162
one[key] = [];
163
mergeDeepIntoArrays(one[key], twoVal, arrayStrategy, level + 1);
164
} else if (twoValIsProperObject) { // [K]
165
mergeDeepIntoObjects(oneVal, twoVal, arrayStrategy, level + 1);
166
} else if (!twoValIsPresent) { // [L]
167
// Leave the left Object alone
168
}
169
} else if (!oneValIsPresent) {
170
if (twoValIsTerminal) { // [M]
171
one[key] = twoVal;
172
} else if (twoValIsArray) { // [N]
173
one[key] = [];
174
mergeDeepIntoArrays(one[key], twoVal, arrayStrategy, level + 1);
175
} else if (twoValIsProperObject) { // [O]
176
one[key] = {};
177
mergeDeepIntoObjects(one[key], twoVal, arrayStrategy, level + 1);
178
} else if (!twoValIsPresent) {
179
// Could/should never happen
180
}
181
}
182
};
183
184
185
186
/**
187
* Provides same functionality as mergeInto, but merges by mutating the first
188
* argument. Will never mutate the second argument.
189
*
190
* mergeDeepInto provides two guarantees:
191
* 1. In the process of mutating one, will not mutate two.
192
* 2. Will not cause any nonTerminal memory to be shared between one and two.
193
* This means that no further mutations to one will effect two (unless some
194
* other part of your application forms shared memory between one and two).
195
*
196
* mergeDeepInto does not guarantee that it will *preserve* any shared memory
197
* between two and one that existed before invocation. After calling
198
* mergeDeepInto, there will be less (or equal) amount of shared memory between
199
* one and two.
200
*
201
* Will tolerate a circular structure as the first parameter, but not the
202
* second.
203
*
204
* Requires that first parameter be a non-Array, non-terminal `Object`, and the
205
* second argument either be an `Object` or `null/undefined`. Arrays may exist
206
* in the depths of either `Object` as long as an `arrayStrategy` is supplied.
207
*
208
* The third parameter indicates how merging two `Array`s should be performed.
209
*
210
* @param {!Object} one Object to be mutated deeply.
211
* @param {?Object} two Values from two take precedence over values in one.
212
* @param {?Enum=} arrayStrategy One of arrayStrategy
213
*/
214
var mergeDeepInto = function(one, twoParam, arrayStrategy) {
215
var two = normalizeMergeArg(twoParam);
216
checkArrayStrategy(arrayStrategy); // Will be checked twice, for now.
217
mergeDeepIntoObjects(one, two, arrayStrategy, 0);
218
};
219
220
module.exports = mergeDeepInto;
221
222