@hkdigital/lib-sveltekit 0.1.6 → 0.1.7
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 +127 -127
- package/dist/classes/data/IterableTree.js +243 -243
- package/dist/classes/data/Selector.js +190 -190
- package/dist/classes/data/index.js +2 -2
- package/dist/classes/index.js +3 -3
- package/dist/classes/promise/HkPromise.js +377 -377
- package/dist/classes/promise/index.js +1 -1
- package/dist/classes/stores/SubscribersCount.js +107 -107
- package/dist/classes/stores/index.js +1 -1
- package/dist/classes/streams/LogTransformStream.js +19 -19
- package/dist/classes/streams/ServerEventsStore.js +110 -110
- package/dist/classes/streams/TimeStampSource.js +26 -26
- package/dist/classes/streams/index.js +3 -3
- package/dist/classes/svelte/audio/AudioLoader.svelte.js +58 -58
- package/dist/classes/svelte/audio/AudioScene.svelte.js +282 -282
- package/dist/classes/svelte/audio/mocks.js +35 -35
- package/dist/classes/svelte/final-state-machine/FiniteStateMachine.svelte.js +133 -133
- package/dist/classes/svelte/final-state-machine/index.js +1 -1
- package/dist/classes/svelte/image/ImageLoader.svelte.js +47 -47
- package/dist/classes/svelte/image/ImageScene.svelte.js +253 -253
- package/dist/classes/svelte/image/ImageVariantsLoader.svelte.js +152 -152
- package/dist/classes/svelte/image/index.js +4 -4
- package/dist/classes/svelte/image/mocks.js +35 -35
- package/dist/classes/svelte/image/typedef.js +8 -8
- package/dist/classes/svelte/loading-state-machine/LoadingStateMachine.svelte.js +109 -109
- package/dist/classes/svelte/loading-state-machine/constants.js +16 -16
- package/dist/classes/svelte/loading-state-machine/index.js +3 -3
- package/dist/classes/svelte/network-loader/NetworkLoader.svelte.js +331 -331
- package/dist/classes/svelte/network-loader/constants.js +3 -3
- package/dist/classes/svelte/network-loader/index.js +3 -3
- package/dist/classes/svelte/network-loader/mocks.js +30 -30
- package/dist/classes/svelte/network-loader/typedef.js +8 -8
- package/dist/components/area/HkArea.svelte +49 -49
- package/dist/components/area/HkArea.svelte.d.ts +14 -0
- package/dist/components/area/HkGridArea.svelte +77 -77
- package/dist/components/area/HkGridArea.svelte.d.ts +22 -0
- package/dist/components/area/index.js +2 -2
- package/dist/components/boxes/game-box/GameBox.svelte +112 -112
- package/dist/components/boxes/game-box/GameBox.svelte.d.ts +15 -0
- package/dist/components/boxes/game-box/gamebox.util.js +83 -83
- package/dist/components/boxes/index.js +2 -2
- package/dist/components/boxes/virtual-viewport/VirtualViewport.svelte +199 -199
- package/dist/components/boxes/virtual-viewport/VirtualViewport.svelte.d.ts +22 -0
- package/dist/components/buttons/button/Button.svelte +76 -75
- package/dist/components/buttons/button/Button.svelte.d.ts +21 -0
- package/dist/components/buttons/button-icon-steeze/SteezeIconButton.svelte +30 -0
- package/dist/components/buttons/button-icon-steeze/SteezeIconButton.svelte.d.ts +16 -0
- package/dist/components/buttons/button-text/TextButton.svelte +21 -21
- package/dist/components/buttons/button-text/TextButton.svelte.d.ts +7 -0
- package/dist/components/buttons/index.d.ts +1 -0
- package/dist/components/buttons/index.js +3 -2
- package/dist/components/hkdev/blocks/TextBlock.svelte +46 -46
- package/dist/components/hkdev/blocks/TextBlock.svelte.d.ts +13 -0
- package/dist/components/hkdev/buttons/CheckButton.svelte +62 -62
- package/dist/components/hkdev/buttons/CheckButton.svelte.d.ts +18 -0
- package/dist/components/{icon → icons}/HkIcon.svelte +86 -86
- package/dist/components/icons/HkIcon.svelte.d.ts +22 -0
- package/dist/components/{icon → icons}/HkTabIcon.svelte +116 -116
- package/dist/components/icons/HkTabIcon.svelte.d.ts +40 -0
- package/dist/components/icons/SteezeIcon.svelte +96 -0
- package/dist/components/icons/SteezeIcon.svelte.d.ts +22 -0
- package/dist/components/{icon → icons}/index.d.ts +1 -0
- package/dist/components/{icon → icons}/index.js +6 -4
- package/dist/components/{icon → icons}/typedef.js +16 -16
- package/dist/components/image/ImageBox.svelte +208 -208
- package/dist/components/image/ImageBox.svelte.d.ts +19 -0
- package/dist/components/image/index.js +5 -5
- package/dist/components/image/typedef.js +32 -32
- package/dist/components/index.js +2 -2
- package/dist/components/inputs/index.js +1 -1
- package/dist/components/inputs/text-input/TestTextInput.svelte__ +102 -102
- package/dist/components/inputs/text-input/TextInput.svelte +226 -226
- package/dist/components/inputs/text-input/TextInput.svelte.d.ts +28 -0
- package/dist/components/inputs/text-input/TextInput.svelte___ +83 -83
- package/dist/components/inputs/text-input/assets/IconInvalid.svelte +14 -14
- package/dist/components/inputs/text-input/assets/IconValid.svelte +12 -12
- package/dist/components/layout/HkAppLayout.state.svelte.js +25 -25
- package/dist/components/layout/HkAppLayout.svelte +251 -251
- package/dist/components/layout/HkAppLayout.svelte.d.ts +11 -0
- package/dist/components/layout/HkGridLayers.svelte +82 -82
- package/dist/components/layout/HkGridLayers.svelte.d.ts +23 -0
- package/dist/components/layout/index.js +9 -9
- package/dist/components/panels/index.js +1 -1
- package/dist/components/panels/plain-panel/PlainPanel.svelte +33 -33
- package/dist/components/panels/plain-panel/PlainPanel.svelte.d.ts +12 -0
- package/dist/components/rows/index.js +3 -3
- package/dist/components/rows/panel-grid-row/PanelGridRow.svelte +104 -104
- package/dist/components/rows/panel-grid-row/PanelGridRow.svelte.d.ts +14 -0
- package/dist/components/rows/panel-row-2/PanelRow2.svelte +40 -40
- package/dist/components/rows/panel-row-2/PanelRow2.svelte.d.ts +14 -0
- package/dist/components/tab-bar/HkTabBar.state.svelte.js +149 -149
- package/dist/components/tab-bar/HkTabBar.svelte +74 -74
- package/dist/components/tab-bar/HkTabBar.svelte.d.ts +18 -0
- package/dist/components/tab-bar/HkTabBarSelector.state.svelte.js +93 -93
- package/dist/components/tab-bar/HkTabBarSelector.svelte +49 -49
- package/dist/components/tab-bar/HkTabBarSelector.svelte.d.ts +19 -0
- package/dist/components/tab-bar/index.js +17 -17
- package/dist/components/tab-bar/typedef.d.ts +1 -1
- package/dist/components/tab-bar/typedef.js +8 -8
- package/dist/components/widgets/compare-left-right/CompareLeftRight.svelte +179 -179
- package/dist/components/widgets/compare-left-right/CompareLeftRight.svelte.d.ts +10 -0
- package/dist/components/widgets/compare-left-right/index.js +1 -1
- package/dist/components/widgets/scale-control/index.js +1 -1
- package/dist/config/imagetools-config.js +189 -189
- package/dist/config/imagetools.d.ts +71 -71
- package/dist/config/typedef.js +8 -8
- package/dist/constants/errors/api.js +9 -9
- package/dist/constants/errors/generic.js +5 -5
- package/dist/constants/errors/index.js +3 -3
- package/dist/constants/errors/jwt.js +5 -5
- package/dist/constants/http/headers.js +6 -6
- package/dist/constants/http/index.js +2 -2
- package/dist/constants/http/methods.js +2 -2
- package/dist/constants/index.js +3 -3
- package/dist/constants/mime/application.js +5 -5
- package/dist/constants/mime/audio.js +13 -13
- package/dist/constants/mime/image.js +3 -3
- package/dist/constants/mime/index.js +4 -4
- package/dist/constants/mime/text.js +2 -2
- package/dist/constants/regexp/index.js +31 -31
- package/dist/constants/regexp/inspiratie.js__ +95 -95
- package/dist/constants/regexp/text.js +49 -49
- package/dist/constants/regexp/user.js +32 -32
- package/dist/constants/regexp/web.js +3 -3
- package/dist/constants/state-labels/input-states.js +11 -11
- package/dist/constants/state-labels/submit-states.js +4 -4
- package/dist/constants/time.js +28 -28
- package/dist/css/tw-prose.postcss__ +259 -259
- package/dist/css/utilities.postcss +43 -43
- package/dist/design/design-config.js +73 -73
- package/dist/design/tailwind-theme-extend.d.ts +4 -4
- package/dist/design/tailwind-theme-extend.js +160 -151
- package/dist/schemas/index.js +1 -1
- package/dist/schemas/validate-url.js +180 -180
- package/dist/server/index.js +1 -1
- package/dist/server/logger.js +94 -94
- package/dist/states/index.js +1 -1
- package/dist/states/navigation.svelte.js +55 -55
- package/dist/stores/index.js +1 -1
- package/dist/stores/theme.js +80 -80
- package/dist/themes/hkdev/components/blocks/text-block.postcss +40 -40
- package/dist/themes/hkdev/components/boxes/game-box.postcss +13 -13
- package/dist/themes/hkdev/components/buttons/button-icon-steeze.postcss +22 -0
- package/dist/themes/hkdev/components/buttons/button-text.postcss +34 -34
- package/dist/themes/hkdev/components/buttons/button.postcss +138 -138
- package/dist/themes/hkdev/components/buttons/button.postcss__ +40 -0
- package/dist/themes/hkdev/components/buttons/button.postcss___ +91 -0
- package/dist/themes/hkdev/components/buttons/skip-button.postcss +8 -8
- package/dist/themes/hkdev/components/icons/icon-steeze.postcss +22 -0
- package/dist/themes/hkdev/components/inputs/text-input.postcss +108 -108
- package/dist/themes/hkdev/components/panels/plain-panel.postcss +46 -46
- package/dist/themes/hkdev/components/panels/speech-bubble.postcss +52 -52
- package/dist/themes/hkdev/components/rows/panel-grid-row.postcss +7 -7
- package/dist/themes/hkdev/components/rows/panel-row-2.postcss +9 -9
- package/dist/themes/hkdev/components.postcss +61 -55
- package/dist/themes/hkdev/debug.postcss +1 -1
- package/dist/themes/hkdev/global/layout.postcss +39 -39
- package/dist/themes/hkdev/global/on-colors.postcss +53 -53
- package/dist/themes/hkdev/global/text.postcss__ +34 -34
- package/dist/themes/hkdev/global/vars.postcss__ +7 -7
- package/dist/themes/hkdev/globals.postcss +11 -11
- package/dist/themes/hkdev/responsive.postcss +12 -12
- package/dist/themes/hkdev/theme-ext.js +15 -15
- package/dist/themes/hkdev/theme.js +227 -227
- package/dist/themes/index.js +1 -1
- package/dist/util/array/index.js +455 -455
- package/dist/util/compare/index.js +247 -247
- package/dist/util/css/css-vars.js +83 -83
- package/dist/util/css/index.js +1 -1
- package/dist/util/design-system/components/states.js +22 -22
- package/dist/util/design-system/css/clamp.js +66 -66
- package/dist/util/design-system/css/root-design-vars.js +100 -100
- package/dist/util/design-system/index.js +5 -5
- package/dist/util/design-system/layout/scaling.js +97 -97
- package/dist/util/design-system/tailwind.js +289 -289
- package/dist/util/expect/arrays.js +47 -47
- package/dist/util/expect/index.js +259 -259
- package/dist/util/expect/primitives.js +55 -55
- package/dist/util/expect/url.js +60 -60
- package/dist/util/function/index.js +218 -218
- package/dist/util/http/errors.js +97 -97
- package/dist/util/http/headers.js +45 -45
- package/dist/util/http/http-request.js +273 -273
- package/dist/util/http/index.js +22 -22
- package/dist/util/http/json-request.js +143 -143
- package/dist/util/http/mocks.js +65 -65
- package/dist/util/http/response.js +228 -228
- package/dist/util/http/url.js +52 -52
- package/dist/util/image/index.js +86 -86
- package/dist/util/index.js +2 -2
- package/dist/util/is/index.js +140 -140
- package/dist/util/iterate/index.js +234 -234
- package/dist/util/object/index.js +1361 -1361
- package/dist/util/singleton/index.js +97 -97
- package/dist/util/string/index.js +184 -184
- package/dist/util/svelte/index.js +2 -2
- package/dist/util/svelte/observe/index.js +49 -49
- package/dist/util/svelte/state-context/index.js +83 -83
- package/dist/util/svelte/wait/index.js +38 -38
- package/dist/util/sveltekit/index.js +1 -1
- package/dist/util/sveltekit/route-folders/index.js +82 -82
- package/dist/util/time/index.js +339 -339
- package/dist/valibot/date.js__ +10 -10
- package/dist/valibot/index.js +9 -9
- package/dist/valibot/url.js +95 -95
- package/dist/valibot/user.js +23 -23
- package/dist/zod/all.js +33 -33
- package/dist/zod/generic.js +11 -11
- package/dist/zod/javascript.js +32 -32
- package/dist/zod/user.js +16 -16
- package/dist/zod/web.js +52 -52
- package/package.json +99 -99
- package/dist/components/icon/HkIcon.svelte.d.ts +0 -10
- package/dist/components/icon/HkTabIcon.svelte.d.ts +0 -19
- /package/dist/components/{icon → icons}/typedef.d.ts +0 -0
@@ -1,1361 +1,1361 @@
|
|
1
|
-
/* ------------------------------------------------------------------ Imports */
|
2
|
-
|
3
|
-
import * as expect from '../expect/index.js';
|
4
|
-
|
5
|
-
import { equals } from '../compare/index.js';
|
6
|
-
|
7
|
-
import { toArrayPath } from '../array/index.js';
|
8
|
-
|
9
|
-
import { toStringPath } from '../string/index.js';
|
10
|
-
|
11
|
-
import { isIterable } from '../is/index.js';
|
12
|
-
|
13
|
-
import { iterateObjectPaths, iterateObjectEntries } from '../iterate/index.js';
|
14
|
-
|
15
|
-
// ------------------------------------------------------------------- Internals
|
16
|
-
|
17
|
-
const PATH_SEPARATOR = '.';
|
18
|
-
|
19
|
-
/**
|
20
|
-
* Create a human friendly string representation of an array path
|
21
|
-
* - Allows for removal of the last part of the array part
|
22
|
-
*
|
23
|
-
* @param {string[]} arr - Array path to join
|
24
|
-
* @param {number} [lastIndex]
|
25
|
-
* If specified, only parts up and including the last index will
|
26
|
-
* be joined
|
27
|
-
*
|
28
|
-
* @returns {string} path as string
|
29
|
-
*/
|
30
|
-
function display_array_path(arr, lastIndex) {
|
31
|
-
return arr.slice(0, lastIndex).join(PATH_SEPARATOR);
|
32
|
-
}
|
33
|
-
|
34
|
-
const object_to_string = Object.prototype.toString;
|
35
|
-
const has_own_property = Object.prototype.hasOwnProperty;
|
36
|
-
|
37
|
-
/* ------------------------------------------------------------------ Exports */
|
38
|
-
|
39
|
-
export { PATH_SEPARATOR };
|
40
|
-
|
41
|
-
// -----------------------------------------------------------------------------
|
42
|
-
|
43
|
-
/**
|
44
|
-
* Returns true
|
45
|
-
* - if the object has no (iterable) key value pairs
|
46
|
-
* - if the object is an empty array
|
47
|
-
*
|
48
|
-
* @param {object} obj
|
49
|
-
*
|
50
|
-
* @return {boolean}
|
51
|
-
* true if the object has no key value pairs or the supplied object
|
52
|
-
* is `falsey`
|
53
|
-
*/
|
54
|
-
export function isEmpty(obj) {
|
55
|
-
if (!obj) {
|
56
|
-
// object is null or other falsey value
|
57
|
-
return true;
|
58
|
-
}
|
59
|
-
|
60
|
-
expect.object(obj);
|
61
|
-
|
62
|
-
if (/*obj instanceof Array && */ 0 === obj.length) {
|
63
|
-
return true;
|
64
|
-
}
|
65
|
-
|
66
|
-
for (const key in obj) {
|
67
|
-
return false;
|
68
|
-
}
|
69
|
-
|
70
|
-
return true;
|
71
|
-
}
|
72
|
-
|
73
|
-
// -----------------------------------------------------------------------------
|
74
|
-
|
75
|
-
/**
|
76
|
-
* Get the number of (iterable) key value pairs in the specified object
|
77
|
-
*
|
78
|
-
* @param {object} obj
|
79
|
-
*
|
80
|
-
* @return {number} number of iterable key value pairs
|
81
|
-
*/
|
82
|
-
export function objectSize(obj) {
|
83
|
-
expect.object(obj);
|
84
|
-
|
85
|
-
let count = 0;
|
86
|
-
|
87
|
-
// eslint-disable-next-line no-unused-vars
|
88
|
-
for (const _key in obj) {
|
89
|
-
count = count + 1;
|
90
|
-
}
|
91
|
-
|
92
|
-
return count;
|
93
|
-
}
|
94
|
-
|
95
|
-
// -----------------------------------------------------------------------------
|
96
|
-
|
97
|
-
/**
|
98
|
-
* Returns a shallow copy of the object without the properties that
|
99
|
-
* have the value [null] or [undefined]
|
100
|
-
*
|
101
|
-
* @param {object} obj
|
102
|
-
*
|
103
|
-
* @param {string[]} [onlyKeys]
|
104
|
-
* If specified, only the specified keys will be exported
|
105
|
-
*
|
106
|
-
* @returns {object} new object without the null properties
|
107
|
-
*/
|
108
|
-
export function exportNotNull(obj, onlyKeys) {
|
109
|
-
expect.object(obj);
|
110
|
-
|
111
|
-
// if( onlyKeys )
|
112
|
-
// {
|
113
|
-
// expect.array( onlyKeys );
|
114
|
-
// }
|
115
|
-
|
116
|
-
const newObj = {};
|
117
|
-
|
118
|
-
const onlyKeysSet = onlyKeys ? new Set(onlyKeys) : null;
|
119
|
-
|
120
|
-
for (const key in obj) {
|
121
|
-
const value = obj[key];
|
122
|
-
|
123
|
-
if (value !== null && value !== undefined) {
|
124
|
-
if (onlyKeysSet && !onlyKeysSet.has(key)) {
|
125
|
-
continue;
|
126
|
-
}
|
127
|
-
|
128
|
-
newObj[key] = value;
|
129
|
-
}
|
130
|
-
} // end for
|
131
|
-
|
132
|
-
return newObj;
|
133
|
-
}
|
134
|
-
|
135
|
-
// -----------------------------------------------------------------------------
|
136
|
-
|
137
|
-
/**
|
138
|
-
* Returns a shallow copy of the object without the properties that
|
139
|
-
* are `private`
|
140
|
-
* - Private properties are properties that start with an underscore
|
141
|
-
* `_`.
|
142
|
-
*
|
143
|
-
* @param {object} obj
|
144
|
-
*
|
145
|
-
* @param {string[]} [keepKeys]
|
146
|
-
* If specified, the sprecified private keys will be exported (e.g. `_id`)
|
147
|
-
*
|
148
|
-
* @returns {object} new object without the null properties
|
149
|
-
*/
|
150
|
-
export function exportNotPrivate(obj, keepKeys) {
|
151
|
-
expect.object(obj);
|
152
|
-
|
153
|
-
const newObj = {};
|
154
|
-
|
155
|
-
const keepKeysSet = keepKeys ? new Set(keepKeys) : null;
|
156
|
-
|
157
|
-
for (const key in obj) {
|
158
|
-
const value = obj[key];
|
159
|
-
|
160
|
-
if (!key.startsWith('_')) {
|
161
|
-
newObj[key] = value;
|
162
|
-
} else if (keepKeysSet && keepKeysSet.has(key)) {
|
163
|
-
//
|
164
|
-
// Add key to keep as read only property
|
165
|
-
//
|
166
|
-
Object.defineProperty(newObj, key, {
|
167
|
-
value,
|
168
|
-
writable: false,
|
169
|
-
enumerable: true
|
170
|
-
});
|
171
|
-
}
|
172
|
-
} // end for
|
173
|
-
|
174
|
-
return newObj;
|
175
|
-
}
|
176
|
-
|
177
|
-
// -----------------------------------------------------------------------------
|
178
|
-
|
179
|
-
// export function removeNull()
|
180
|
-
|
181
|
-
// -----------------------------------------------------------------------------
|
182
|
-
|
183
|
-
/**
|
184
|
-
* Keep only the specified keys in the object
|
185
|
-
* - deletes all other key-value pairs in the object
|
186
|
-
*
|
187
|
-
* @param {object} obj
|
188
|
-
* @param {string[]|Set} keys
|
189
|
-
* @param {boolean} [removeNullAndUndefined=true]
|
190
|
-
*
|
191
|
-
* @returns {object} object that only contains the specified keys
|
192
|
-
*/
|
193
|
-
export function keep(obj, keys, removeNullAndUndefined = true) {
|
194
|
-
expect.object(obj);
|
195
|
-
expect.arrayOrSet(keys);
|
196
|
-
|
197
|
-
const keep = keys instanceof Set ? keys : new Set(keys);
|
198
|
-
|
199
|
-
for (const key in obj) {
|
200
|
-
if (!keep.has(key)) {
|
201
|
-
delete obj[key];
|
202
|
-
continue;
|
203
|
-
}
|
204
|
-
|
205
|
-
const value = obj[key];
|
206
|
-
|
207
|
-
if (removeNullAndUndefined) {
|
208
|
-
if (value === null || value === undefined) {
|
209
|
-
delete obj[key];
|
210
|
-
}
|
211
|
-
}
|
212
|
-
} // end for
|
213
|
-
|
214
|
-
return obj;
|
215
|
-
}
|
216
|
-
|
217
|
-
// -----------------------------------------------------------------------------
|
218
|
-
|
219
|
-
/**
|
220
|
-
* Freezes an object recursively
|
221
|
-
* - Allows non-objects to be passed as input parameter (non-objects are
|
222
|
-
* immutable by default).
|
223
|
-
*
|
224
|
-
* @param {any} value
|
225
|
-
*
|
226
|
-
* @returns {any}
|
227
|
-
* recursively frozen object or original input value if a non-object was
|
228
|
-
* supplied as input parameter
|
229
|
-
*/
|
230
|
-
export function deepFreeze(value, _found) {
|
231
|
-
if (!(value instanceof Object)) {
|
232
|
-
return value;
|
233
|
-
}
|
234
|
-
|
235
|
-
if (!_found) {
|
236
|
-
_found = new Set();
|
237
|
-
} else if (_found.has(value)) {
|
238
|
-
// Using recursion -> no need to return value
|
239
|
-
return;
|
240
|
-
}
|
241
|
-
|
242
|
-
_found.add(value);
|
243
|
-
|
244
|
-
Object.freeze(value);
|
245
|
-
|
246
|
-
for (const key in value) {
|
247
|
-
const childObj = value[key];
|
248
|
-
|
249
|
-
if (childObj instanceof Object) {
|
250
|
-
// Recurse into child objects
|
251
|
-
deepFreeze(childObj, _found);
|
252
|
-
}
|
253
|
-
} // end for
|
254
|
-
|
255
|
-
return value;
|
256
|
-
}
|
257
|
-
|
258
|
-
// -----------------------------------------------------------------------------
|
259
|
-
|
260
|
-
/**
|
261
|
-
* Set a value in an object using a path and value pair.
|
262
|
-
* - Automatically creates parent objects
|
263
|
-
*
|
264
|
-
* @param {object} obj - Object to set the value in
|
265
|
-
* @param {string|Array} path - Dot separated string path or array path
|
266
|
-
* @param {any} value - value to set
|
267
|
-
*
|
268
|
-
* @returns {boolean} true if the value was changed
|
269
|
-
*/
|
270
|
-
export function objectSet(obj, path, value) {
|
271
|
-
expect.object(obj);
|
272
|
-
|
273
|
-
const arrPath = toArrayPath(path);
|
274
|
-
|
275
|
-
if (arguments.length < 3) {
|
276
|
-
throw new Error('Missing or invalid parameter [value]');
|
277
|
-
}
|
278
|
-
|
279
|
-
let parentNode;
|
280
|
-
const lastKey = arrPath[arrPath.length - 1];
|
281
|
-
|
282
|
-
if (value !== undefined) {
|
283
|
-
parentNode = _ensureParent(obj, arrPath);
|
284
|
-
} else {
|
285
|
-
//
|
286
|
-
// value is undefined -> delete node
|
287
|
-
//
|
288
|
-
parentNode = _getParent(obj, arrPath);
|
289
|
-
|
290
|
-
if (Array.isArray(parentNode)) {
|
291
|
-
const keyAsInt = parseInt(lastKey, 10);
|
292
|
-
|
293
|
-
if (Number.isNaN(keyAsInt)) {
|
294
|
-
throw new Error(
|
295
|
-
'Cannot delete property [' +
|
296
|
-
arrPath.join(PATH_SEPARATOR) +
|
297
|
-
'] ' +
|
298
|
-
'from data node of type [Array]'
|
299
|
-
);
|
300
|
-
}
|
301
|
-
|
302
|
-
if (keyAsInt < parentNode.length) {
|
303
|
-
parentNode.splice(keyAsInt, 1);
|
304
|
-
return true;
|
305
|
-
}
|
306
|
-
|
307
|
-
return false;
|
308
|
-
} else if (parentNode) {
|
309
|
-
if (lastKey in parentNode) {
|
310
|
-
delete parentNode[lastKey];
|
311
|
-
return true;
|
312
|
-
}
|
313
|
-
return false;
|
314
|
-
}
|
315
|
-
}
|
316
|
-
|
317
|
-
// -- Set value
|
318
|
-
|
319
|
-
const existingValue = parentNode[lastKey];
|
320
|
-
|
321
|
-
if (!equals(value, existingValue)) {
|
322
|
-
parentNode[lastKey] = value;
|
323
|
-
|
324
|
-
return true;
|
325
|
-
}
|
326
|
-
|
327
|
-
return false;
|
328
|
-
}
|
329
|
-
|
330
|
-
// -----------------------------------------------------------------------------
|
331
|
-
|
332
|
-
/**
|
333
|
-
* Removes a value at the specified object path from the object.
|
334
|
-
* - All parent objects that remain empty will be removed too (recursively)
|
335
|
-
*
|
336
|
-
* @param {object} obj - Object to set the value in
|
337
|
-
* @param {string|Array} path - Dot separated string path or array path
|
338
|
-
* @param {any} value - value to set
|
339
|
-
*/
|
340
|
-
export function deletePath(obj, path) {
|
341
|
-
expect.object(obj);
|
342
|
-
|
343
|
-
const arrPath = toArrayPath(path);
|
344
|
-
|
345
|
-
const n = arrPath.length;
|
346
|
-
const n_1 = n - 1;
|
347
|
-
|
348
|
-
if (!n) {
|
349
|
-
// Path is empty ""
|
350
|
-
return;
|
351
|
-
}
|
352
|
-
|
353
|
-
const lastKey = arrPath[n_1];
|
354
|
-
|
355
|
-
if (1 === n) {
|
356
|
-
// Path consist of a single key
|
357
|
-
delete obj[lastKey];
|
358
|
-
return;
|
359
|
-
}
|
360
|
-
|
361
|
-
// path is longer than a single key >>
|
362
|
-
|
363
|
-
// -- Get parent objects
|
364
|
-
|
365
|
-
const parents = [];
|
366
|
-
|
367
|
-
let current = obj;
|
368
|
-
|
369
|
-
let endValueFound = true;
|
370
|
-
|
371
|
-
for (let j = 0; j < n; j = j + 1) {
|
372
|
-
if (!(current instanceof Object)) {
|
373
|
-
break;
|
374
|
-
}
|
375
|
-
|
376
|
-
parents.push(current);
|
377
|
-
|
378
|
-
const key = arrPath[j];
|
379
|
-
|
380
|
-
// console.log(
|
381
|
-
// {
|
382
|
-
// current,
|
383
|
-
// key,
|
384
|
-
// next: current[ key ]
|
385
|
-
// } );
|
386
|
-
|
387
|
-
if (!(key in current)) {
|
388
|
-
// child not found -> no more parents
|
389
|
-
endValueFound = false;
|
390
|
-
break;
|
391
|
-
}
|
392
|
-
|
393
|
-
current = current[key];
|
394
|
-
}
|
395
|
-
|
396
|
-
// console.log( "parents", parents );
|
397
|
-
|
398
|
-
// -- Delete value from direct parent
|
399
|
-
|
400
|
-
const n_parents = parents.length - 1;
|
401
|
-
|
402
|
-
if (endValueFound) {
|
403
|
-
const lastParent = parents[n_parents];
|
404
|
-
|
405
|
-
if (!Array.isArray(lastParent)) {
|
406
|
-
delete lastParent[lastKey];
|
407
|
-
} else {
|
408
|
-
lastParent.splice(parseInt(lastKey, 10), 1);
|
409
|
-
}
|
410
|
-
}
|
411
|
-
|
412
|
-
// -- Remove empty parents
|
413
|
-
|
414
|
-
for (let j = n_parents - 1; j >= 0; j = j - 1) {
|
415
|
-
const parent = parents[j];
|
416
|
-
const key = arrPath[j];
|
417
|
-
const child = parent[key];
|
418
|
-
|
419
|
-
let childIsEmpty = false;
|
420
|
-
|
421
|
-
if (Array.isArray(child)) {
|
422
|
-
// Child is array
|
423
|
-
if (0 === child.length) {
|
424
|
-
childIsEmpty = true;
|
425
|
-
}
|
426
|
-
} else {
|
427
|
-
// Child is object
|
428
|
-
if (0 === Object.keys(child).length) {
|
429
|
-
childIsEmpty = true;
|
430
|
-
}
|
431
|
-
}
|
432
|
-
|
433
|
-
if (!childIsEmpty) {
|
434
|
-
// done
|
435
|
-
break;
|
436
|
-
}
|
437
|
-
|
438
|
-
// Remove empty child from parent
|
439
|
-
|
440
|
-
if (!Array.isArray(parent)) {
|
441
|
-
delete parent[key];
|
442
|
-
break;
|
443
|
-
} else {
|
444
|
-
parent.splice(parseInt(key, 10), 1);
|
445
|
-
}
|
446
|
-
} // end for
|
447
|
-
}
|
448
|
-
|
449
|
-
// -----------------------------------------------------------------------------
|
450
|
-
|
451
|
-
/**
|
452
|
-
* Get a value from an object using a path
|
453
|
-
* - Returns a default value if not found, with is [undefined] by default
|
454
|
-
*
|
455
|
-
* @param {object} obj - Object to get the value from
|
456
|
-
* @param {string|Array} path - Dot separated string path or array path
|
457
|
-
*
|
458
|
-
* @param {any} [defaultValue=undefined]
|
459
|
-
* Value to return if the value does not exist
|
460
|
-
*
|
461
|
-
* @return {any} value found at path, defaultValue or undefined
|
462
|
-
*/
|
463
|
-
export function objectGet(obj, path, defaultValue) {
|
464
|
-
expect.object(obj);
|
465
|
-
|
466
|
-
const arrPath = toArrayPath(path);
|
467
|
-
|
468
|
-
if (!path.length || (1 === path.length && !path[0].length)) {
|
469
|
-
// "" or [""]
|
470
|
-
return obj;
|
471
|
-
}
|
472
|
-
|
473
|
-
const parentNode = _getParent(obj, arrPath);
|
474
|
-
|
475
|
-
if (!parentNode) {
|
476
|
-
return defaultValue; // @note may be undefined
|
477
|
-
}
|
478
|
-
|
479
|
-
const lastKey = arrPath[arrPath.length - 1];
|
480
|
-
|
481
|
-
const value = parentNode[lastKey];
|
482
|
-
|
483
|
-
if (value === undefined) {
|
484
|
-
return defaultValue; // @note may be undefined
|
485
|
-
}
|
486
|
-
|
487
|
-
return value;
|
488
|
-
}
|
489
|
-
|
490
|
-
// -----------------------------------------------------------------------------
|
491
|
-
|
492
|
-
/**
|
493
|
-
* Get a value from an object using a path
|
494
|
-
* - Throws an exception if the path does not exist or the value is undefined
|
495
|
-
*
|
496
|
-
* @param {object} obj - Object to get the value from
|
497
|
-
* @param {string|Array} path - Dot separated string path or array path
|
498
|
-
*
|
499
|
-
* @param {function} [parseFn]
|
500
|
-
* Optional parser function that checks and converts the value
|
501
|
-
*
|
502
|
-
* @throws No value found at path
|
503
|
-
* @throws Invalid value
|
504
|
-
*
|
505
|
-
* @return {any} value found at path
|
506
|
-
*/
|
507
|
-
export function objectGetWithThrow(obj, path, parseFn) {
|
508
|
-
let value = objectGet(obj, path);
|
509
|
-
|
510
|
-
if (parseFn) {
|
511
|
-
const { value: parsedValue, error } = parseFn(value);
|
512
|
-
|
513
|
-
if (error) {
|
514
|
-
throw new Error(`Invalid value found at path [${toStringPath(path)}]`, { cause: error });
|
515
|
-
}
|
516
|
-
|
517
|
-
value = parsedValue;
|
518
|
-
}
|
519
|
-
|
520
|
-
if (value === undefined) {
|
521
|
-
throw new Error(`No value found at path [${toStringPath(path)}]`);
|
522
|
-
}
|
523
|
-
|
524
|
-
return value;
|
525
|
-
}
|
526
|
-
|
527
|
-
// -----------------------------------------------------------------------------
|
528
|
-
|
529
|
-
// DEV >>>>
|
530
|
-
|
531
|
-
/**
|
532
|
-
* Get an iterator that returns the value of a path for each item (object)
|
533
|
-
* in the list of objects
|
534
|
-
*
|
535
|
-
* @param {object[]} arr - Array of objects
|
536
|
-
* @param {string|string[]} path - Dot separated string path or array path
|
537
|
-
*
|
538
|
-
* @param {object} [options] - options
|
539
|
-
*
|
540
|
-
* DEPRECEATED >>> NOT COMPATIBLE WITH LIGHTWEIGHT ITERATOR
|
541
|
-
* @param {object} [options.unique=false] - Only return unique values
|
542
|
-
*
|
543
|
-
* @param {any} [options.defaultValue]
|
544
|
-
* Value to return if the value does not exist
|
545
|
-
*
|
546
|
-
* @returns {Iterator<mixed>} value at the specified path for each item
|
547
|
-
*/
|
548
|
-
// export function values( arr, path, options )
|
549
|
-
// {
|
550
|
-
// if( !Array.isArray(arr) )
|
551
|
-
// {
|
552
|
-
// throw new Error("Missing or invalid parameter [arr] (expected Array)");
|
553
|
-
// }
|
554
|
-
|
555
|
-
// if( typeof path !== "string" && !Array.isArray(path) )
|
556
|
-
// {
|
557
|
-
// throw new Error(
|
558
|
-
// "Missing or invalid parameter [path] (expected string or Array");
|
559
|
-
// }
|
560
|
-
|
561
|
-
// options = Object.assign(
|
562
|
-
// {
|
563
|
-
// unique: false,
|
564
|
-
// defaultValue: undefined
|
565
|
-
// },
|
566
|
-
// options );
|
567
|
-
|
568
|
-
// if( options.unique )
|
569
|
-
// {
|
570
|
-
// // Keep track of all values to prevent duplicates
|
571
|
-
// }
|
572
|
-
|
573
|
-
// throw new Error("NOT IMPLEMENTED YET");
|
574
|
-
// }
|
575
|
-
|
576
|
-
// <<< DEV
|
577
|
-
|
578
|
-
// -----------------------------------------------------------------------------
|
579
|
-
|
580
|
-
/**
|
581
|
-
* Returns a list of differences between the object before and the object
|
582
|
-
* after the changes.
|
583
|
-
* - By default, the function returns changes for added, updated and removed
|
584
|
-
* properties
|
585
|
-
*
|
586
|
-
* @param {object} objBefore
|
587
|
-
* @param {object} objAfter
|
588
|
-
*
|
589
|
-
* @param {object} options
|
590
|
-
* @param {boolean} [options.ignoreAdd=false]
|
591
|
-
* @param {boolean} [options.ignoreUpdate=false]
|
592
|
-
* @param {boolean} [options.ignoreDelete=false]
|
593
|
-
*
|
594
|
-
* @param {boolean} [options.ignorePrivate=true]
|
595
|
-
* Ignore properties that start with an underscore e.g. _id or _updatedAt
|
596
|
-
*
|
597
|
-
* @param {boolean} [options.deleteValue=null]
|
598
|
-
*
|
599
|
-
* @returns {array}
|
600
|
-
* List of changes between the object before and object after
|
601
|
-
*/
|
602
|
-
export function objectDiff(objBefore, objAfter, options = {}, _recursion) {
|
603
|
-
const changes = [];
|
604
|
-
|
605
|
-
const ignoreAdd = undefined === options.ignoreAdd ? false : options.ignoreAdd;
|
606
|
-
|
607
|
-
const ignoreUpdate = undefined === options.ignoreUpdate ? false : options.ignoreUpdate;
|
608
|
-
|
609
|
-
const ignoreDelete = undefined === options.ignoreDelete ? false : options.ignoreDelete;
|
610
|
-
|
611
|
-
const ignorePrivate = undefined === options.ignorePrivate ? true : options.ignorePrivate;
|
612
|
-
|
613
|
-
let deleteValue = null;
|
614
|
-
|
615
|
-
if ('deletevalue' in options) {
|
616
|
-
// Might be [undefined]
|
617
|
-
deleteValue = options.deleteValue;
|
618
|
-
}
|
619
|
-
|
620
|
-
objBefore = objBefore || {};
|
621
|
-
|
622
|
-
for (const key in objAfter) {
|
623
|
-
if (ignorePrivate && key.startsWith('_')) {
|
624
|
-
continue;
|
625
|
-
}
|
626
|
-
|
627
|
-
const newValue = objAfter[key];
|
628
|
-
|
629
|
-
if (deleteValue !== newValue) {
|
630
|
-
const previousValue = objBefore[key];
|
631
|
-
|
632
|
-
if (newValue === previousValue) {
|
633
|
-
// No change
|
634
|
-
continue;
|
635
|
-
}
|
636
|
-
|
637
|
-
if (previousValue instanceof Object && newValue instanceof Object) {
|
638
|
-
// TODO: _recursion
|
639
|
-
|
640
|
-
const diff = objectDiff(newValue, previousValue, options, _recursion);
|
641
|
-
|
642
|
-
if (0 === diff.length) {
|
643
|
-
// No change
|
644
|
-
continue;
|
645
|
-
}
|
646
|
-
}
|
647
|
-
|
648
|
-
if (undefined !== previousValue) {
|
649
|
-
if (!ignoreUpdate) {
|
650
|
-
// update property value
|
651
|
-
changes.push({ path: key, set: newValue });
|
652
|
-
}
|
653
|
-
} else if (!ignoreAdd) {
|
654
|
-
// add property
|
655
|
-
changes.push({ path: key, set: newValue });
|
656
|
-
}
|
657
|
-
} else if (!ignoreDelete) {
|
658
|
-
// newValue === deleteValue && ignoreDelete=true
|
659
|
-
changes.push({ path: key, unset: 1 });
|
660
|
-
}
|
661
|
-
} // end for
|
662
|
-
|
663
|
-
return changes;
|
664
|
-
}
|
665
|
-
|
666
|
-
// -----------------------------------------------------------------------------
|
667
|
-
|
668
|
-
/**
|
669
|
-
* Applies a list of differences to the input object
|
670
|
-
* - A list of changes can be generated by e.g. objectDiff
|
671
|
-
*
|
672
|
-
* @param {object} obj
|
673
|
-
* @param {object[]} changes
|
674
|
-
*
|
675
|
-
* @param {object} options
|
676
|
-
* @param {boolean} [options.ignoreAdd=false]
|
677
|
-
* @param {boolean} [options.ignoreUpdate=false]
|
678
|
-
* @param {boolean} [options.ignoreDelete=false]
|
679
|
-
*
|
680
|
-
* @param {boolean} [options.ignorePrivate=true]
|
681
|
-
* Ignore properties that start with an underscore e.g. _id or _updatedAt
|
682
|
-
*/
|
683
|
-
export function patchObject(obj, changes, options = {}) {
|
684
|
-
expect.object(obj);
|
685
|
-
expect.array(changes);
|
686
|
-
|
687
|
-
const ignoreAdd = undefined === options.ignoreAdd ? false : options.ignoreAdd;
|
688
|
-
|
689
|
-
const ignoreUpdate = undefined === options.ignoreUpdate ? false : options.ignoreUpdate;
|
690
|
-
|
691
|
-
const ignoreDelete = undefined === options.ignoreDelete ? false : options.ignoreDelete;
|
692
|
-
|
693
|
-
const ignorePrivate = undefined === options.ignorePrivate ? true : options.ignorePrivate;
|
694
|
-
|
695
|
-
for (let j = 0, n = changes.length; j < n; j = j + 1) {
|
696
|
-
const change = changes[j];
|
697
|
-
|
698
|
-
const path = change.path;
|
699
|
-
|
700
|
-
// FIXME: recursion
|
701
|
-
|
702
|
-
if (ignorePrivate && path.startsWith('_')) {
|
703
|
-
continue;
|
704
|
-
}
|
705
|
-
|
706
|
-
if ('unset' in change) {
|
707
|
-
if (ignoreDelete) {
|
708
|
-
continue;
|
709
|
-
}
|
710
|
-
|
711
|
-
// console.log( "DELETE", change );
|
712
|
-
objectSet(obj, path, undefined); // undefined deletes property
|
713
|
-
continue;
|
714
|
-
}
|
715
|
-
|
716
|
-
const newValue = change.set;
|
717
|
-
|
718
|
-
if (undefined === newValue) {
|
719
|
-
//logError("Invalid [change]", ignoreDelete, change);
|
720
|
-
throw new Error(`Cannot set value [${path}=undefined]`);
|
721
|
-
}
|
722
|
-
|
723
|
-
if (ignoreAdd && Object._get(obj, path) === undefined) {
|
724
|
-
// Ignore add
|
725
|
-
continue;
|
726
|
-
} else if (ignoreUpdate && objectGet(obj, path) !== undefined) {
|
727
|
-
// Ignore update
|
728
|
-
continue;
|
729
|
-
}
|
730
|
-
|
731
|
-
// Set new value
|
732
|
-
objectSet(obj, path, newValue);
|
733
|
-
} // end for
|
734
|
-
}
|
735
|
-
|
736
|
-
// -----------------------------------------------------------------------------
|
737
|
-
|
738
|
-
/**
|
739
|
-
* Extend the target object with methods and properties from the source
|
740
|
-
* property object
|
741
|
-
* - The target object will be extended by inserting the source property
|
742
|
-
* object into it's property chain
|
743
|
-
|
744
|
-
* - If the current target's prototype is not the same as the source
|
745
|
-
* prototype, the source prototype will be cloned
|
746
|
-
*
|
747
|
-
* @param {object} target - Target object
|
748
|
-
* @param {object} source
|
749
|
-
* object to append to the target's prototype chain. The object and it's
|
750
|
-
* prototype objects are cloned first
|
751
|
-
*/
|
752
|
-
export function extend(target, source) {
|
753
|
-
expect.objectNoFunction(target);
|
754
|
-
|
755
|
-
if (Object.isFrozen(target)) {
|
756
|
-
throw new Error('Invalid parameter [target] (object is immutable)');
|
757
|
-
}
|
758
|
-
|
759
|
-
expect.objectNoFunction(source);
|
760
|
-
|
761
|
-
// let sourceProto = source.prototype;
|
762
|
-
|
763
|
-
let targetProto = Object.getPrototypeOf(target);
|
764
|
-
|
765
|
-
if (targetProto === Object.prototype || Object.isFrozen(targetProto)) {
|
766
|
-
// FIXME: || Object.isFrozen(targetProto) should not be necessary!
|
767
|
-
|
768
|
-
targetProto = target;
|
769
|
-
}
|
770
|
-
|
771
|
-
{
|
772
|
-
let obj = source;
|
773
|
-
|
774
|
-
while (obj && obj !== Object.prototype) {
|
775
|
-
const next = Object.getPrototypeOf(obj);
|
776
|
-
|
777
|
-
copyOwnProperties(obj, targetProto);
|
778
|
-
|
779
|
-
obj = next;
|
780
|
-
}
|
781
|
-
}
|
782
|
-
|
783
|
-
return;
|
784
|
-
}
|
785
|
-
|
786
|
-
// -----------------------------------------------------------------------------
|
787
|
-
|
788
|
-
/**
|
789
|
-
* Get a list of property names of the specified object
|
790
|
-
*
|
791
|
-
* @param {object} obj
|
792
|
-
*
|
793
|
-
* @returns {string[]} List of property names
|
794
|
-
*/
|
795
|
-
export function getPrototypeNames(obj) {
|
796
|
-
expect.object(obj);
|
797
|
-
|
798
|
-
let proto = obj.prototype || obj;
|
799
|
-
|
800
|
-
const names = [];
|
801
|
-
|
802
|
-
while (proto) {
|
803
|
-
// console.log( proto );
|
804
|
-
|
805
|
-
names.push(proto.constructor.name);
|
806
|
-
|
807
|
-
proto = Object.getPrototypeOf(proto);
|
808
|
-
}
|
809
|
-
|
810
|
-
return names;
|
811
|
-
}
|
812
|
-
|
813
|
-
// -----------------------------------------------------------------------------
|
814
|
-
|
815
|
-
/**
|
816
|
-
* Get a tree of values from an object
|
817
|
-
* - Can returns default values if one or multiple values were not found
|
818
|
-
*
|
819
|
-
* @param {object} obj - Object to get the value from
|
820
|
-
* @param {object} tree
|
821
|
-
* Tree that contains the object paths to get.
|
822
|
-
*
|
823
|
-
* e.g. { path: 1 }
|
824
|
-
* { some: { path: { to: 1 }, otherPath: { to: 1 } } }
|
825
|
-
* { "some.path.to": 1, "some.otherPath.to": 1 }
|
826
|
-
* { someArray.0.name: 1 }
|
827
|
-
*
|
828
|
-
* @param {object} [options]
|
829
|
-
* @param {object} [options.shallowLeaves=false]
|
830
|
-
* If set to true, the values of the leaves of the tree will be converted
|
831
|
-
* to shallow objects if the leaves are nested objects.
|
832
|
-
*
|
833
|
-
* @return {object}
|
834
|
-
* nested object with the values that were found or defaultValues
|
835
|
-
*/
|
836
|
-
export function getTree(obj, tree, options) {
|
837
|
-
expect.object(obj);
|
838
|
-
expect.object(tree);
|
839
|
-
|
840
|
-
let shallowLeaves = false;
|
841
|
-
|
842
|
-
if (options instanceof Object && options.shallowLeaves) {
|
843
|
-
shallowLeaves = true;
|
844
|
-
}
|
845
|
-
|
846
|
-
const it = iterateObjectPaths(tree, { walkArrays: true });
|
847
|
-
|
848
|
-
const result = {};
|
849
|
-
|
850
|
-
for (let arrPath of it) {
|
851
|
-
// console.log( { arrPath } );
|
852
|
-
|
853
|
-
if (1 === arrPath.length && arrPath[0].includes(PATH_SEPARATOR)) {
|
854
|
-
// Convert "short path syntax" to array path
|
855
|
-
arrPath = arrPath[0].split(PATH_SEPARATOR);
|
856
|
-
}
|
857
|
-
|
858
|
-
// -- Get value from object at current path
|
859
|
-
|
860
|
-
const leaveValue = objectGet(obj, arrPath);
|
861
|
-
|
862
|
-
// -- Set value in result object at current path
|
863
|
-
|
864
|
-
if (!shallowLeaves || !(leaveValue instanceof Object)) {
|
865
|
-
// option: shallowLeaves=false OR value is not an object
|
866
|
-
// -> no need to convert leaves to shallow objects
|
867
|
-
objectSet(result, arrPath, leaveValue);
|
868
|
-
} else {
|
869
|
-
// Set shallow leave value instead of nested object
|
870
|
-
|
871
|
-
const shallowLeaveValue = shallowClone(leaveValue);
|
872
|
-
objectSet(result, arrPath, shallowLeaveValue);
|
873
|
-
}
|
874
|
-
} // end for
|
875
|
-
|
876
|
-
return result;
|
877
|
-
}
|
878
|
-
|
879
|
-
// -----------------------------------------------------------------------------
|
880
|
-
|
881
|
-
/**
|
882
|
-
* Deep clone an object or any kind of other variable
|
883
|
-
* - Recursively clone all nested properties
|
884
|
-
* - Properties in the prototype chain are cloned too, but all copied into a
|
885
|
-
* single prototype object
|
886
|
-
* - This method works on objects, but also on any other JS variable type
|
887
|
-
* - If a value cannot be cloned, a reference is returned. o.a. for
|
888
|
-
* - Error objects
|
889
|
-
* - Browser objects
|
890
|
-
* - Functions
|
891
|
-
*
|
892
|
-
* @param {any} objectToBeCloned - Variable to clone
|
893
|
-
*
|
894
|
-
* @returns {any} cloned output
|
895
|
-
*/
|
896
|
-
export function clone(objectToBeCloned, _seenObjects) {
|
897
|
-
// const startTime = Date.now();
|
898
|
-
|
899
|
-
// -- Return references for all variables that are not objects
|
900
|
-
|
901
|
-
if (!(objectToBeCloned instanceof Object)) {
|
902
|
-
return objectToBeCloned;
|
903
|
-
}
|
904
|
-
|
905
|
-
// --- Check if variable has already been cloned before
|
906
|
-
|
907
|
-
if (!_seenObjects) {
|
908
|
-
_seenObjects = { originals: [], clones: [] };
|
909
|
-
} else {
|
910
|
-
const originals = _seenObjects.originals;
|
911
|
-
|
912
|
-
if (originals.length > 0) {
|
913
|
-
const foundIndex = originals.indexOf(objectToBeCloned);
|
914
|
-
|
915
|
-
if (-1 !== foundIndex) {
|
916
|
-
//console.log("(recursion) return
|
917
|
-
//from [_seenObjects]", _seenObjects.clones, foundIndex);
|
918
|
-
return _seenObjects.clones[foundIndex];
|
919
|
-
}
|
920
|
-
}
|
921
|
-
}
|
922
|
-
|
923
|
-
// -- Handle array (like) objects
|
924
|
-
|
925
|
-
const typeString = object_to_string.call(objectToBeCloned);
|
926
|
-
|
927
|
-
if (Array.isArray(objectToBeCloned) || '[object Arguments]' === typeString) {
|
928
|
-
const objectClone = [];
|
929
|
-
|
930
|
-
for (let j = 0, n = objectToBeCloned.length; j < n; j = j + 1) {
|
931
|
-
objectClone[j] = clone(objectToBeCloned[j], _seenObjects);
|
932
|
-
}
|
933
|
-
|
934
|
-
_seenObjects.originals.push(objectToBeCloned);
|
935
|
-
_seenObjects.clones.push(objectClone);
|
936
|
-
|
937
|
-
return objectClone;
|
938
|
-
}
|
939
|
-
|
940
|
-
// -- Handle not clonable objects
|
941
|
-
|
942
|
-
if ('[object Object]' !== typeString || objectToBeCloned instanceof Error) {
|
943
|
-
// Functions, Browser objects, ...
|
944
|
-
// - Not clonable -> return reference
|
945
|
-
// - No need to add to _seenObjects
|
946
|
-
return objectToBeCloned;
|
947
|
-
}
|
948
|
-
|
949
|
-
// -- Handle special objects
|
950
|
-
{
|
951
|
-
// Filter out special objects.
|
952
|
-
const Constructor = objectToBeCloned.constructor;
|
953
|
-
|
954
|
-
let objectClone;
|
955
|
-
|
956
|
-
switch (Constructor) {
|
957
|
-
case RegExp:
|
958
|
-
objectClone = new Constructor(objectToBeCloned);
|
959
|
-
break;
|
960
|
-
|
961
|
-
case Date:
|
962
|
-
objectClone = new Constructor(objectToBeCloned.getTime());
|
963
|
-
break;
|
964
|
-
|
965
|
-
// TODO: other special objects ...
|
966
|
-
}
|
967
|
-
|
968
|
-
if (objectClone) {
|
969
|
-
_seenObjects.originals.push(objectToBeCloned);
|
970
|
-
_seenObjects.clones.push(objectClone);
|
971
|
-
|
972
|
-
return objectClone;
|
973
|
-
}
|
974
|
-
|
975
|
-
if (objectToBeCloned.hkDoNotClone) {
|
976
|
-
// Not clonable flag was set -> return reference
|
977
|
-
// TODO: Other objects that should not be cloned?
|
978
|
-
return objectToBeCloned;
|
979
|
-
}
|
980
|
-
}
|
981
|
-
|
982
|
-
// -- Create new object and clone properties
|
983
|
-
|
984
|
-
// objectClone = new Constructor();
|
985
|
-
// const prototypeClone = Object.create(null);
|
986
|
-
|
987
|
-
const prototypeClone = {};
|
988
|
-
const objectClone = Object.create(prototypeClone);
|
989
|
-
|
990
|
-
// Already push object to _seenObjects
|
991
|
-
// (objectClone will still change as properties are being cloned)
|
992
|
-
_seenObjects.originals.push(objectToBeCloned);
|
993
|
-
_seenObjects.clones.push(objectClone);
|
994
|
-
|
995
|
-
for (const prop in objectToBeCloned) {
|
996
|
-
if (has_own_property.call(objectToBeCloned, prop)) {
|
997
|
-
// Own property -> clone into object
|
998
|
-
objectClone[prop] = clone(objectToBeCloned[prop], _seenObjects);
|
999
|
-
} else {
|
1000
|
-
// Inherited property -> clone into prototype
|
1001
|
-
|
1002
|
-
// @warning
|
1003
|
-
// known issue: all inherited properties are copied into the
|
1004
|
-
// same prototype object!
|
1005
|
-
|
1006
|
-
prototypeClone[prop] = clone(objectToBeCloned[prop], _seenObjects);
|
1007
|
-
}
|
1008
|
-
}
|
1009
|
-
|
1010
|
-
return objectClone;
|
1011
|
-
}
|
1012
|
-
|
1013
|
-
// -----------------------------------------------------------------------------
|
1014
|
-
|
1015
|
-
/**
|
1016
|
-
* Set a read only property in an object
|
1017
|
-
*
|
1018
|
-
* @param {object} obj - Object to set the read only property in
|
1019
|
-
* @param {string} propertyName - Name of the property to set
|
1020
|
-
* @param {any} value - Value to set
|
1021
|
-
*/
|
1022
|
-
export function setReadOnlyProperty(obj, propertyName, value) {
|
1023
|
-
expect.object(obj);
|
1024
|
-
|
1025
|
-
expect.string(propertyName);
|
1026
|
-
|
1027
|
-
// expect.defined(value);
|
1028
|
-
|
1029
|
-
Object.defineProperty(obj, propertyName, {
|
1030
|
-
value,
|
1031
|
-
writable: false,
|
1032
|
-
enumerable: true
|
1033
|
-
});
|
1034
|
-
}
|
1035
|
-
|
1036
|
-
// -----------------------------------------------------------------------------
|
1037
|
-
|
1038
|
-
/**
|
1039
|
-
* Returns a clone of a (nested) object that has a maximum depth
|
1040
|
-
*
|
1041
|
-
* @param {object|array} obj
|
1042
|
-
*
|
1043
|
-
* @returns {object|array} shallow object or array
|
1044
|
-
*/
|
1045
|
-
export function shallowClone(objectOrArray) {
|
1046
|
-
expect.object(objectOrArray);
|
1047
|
-
|
1048
|
-
if (Array.isArray(objectOrArray)) {
|
1049
|
-
// obj is an array
|
1050
|
-
|
1051
|
-
let isShallow = true;
|
1052
|
-
|
1053
|
-
for (let j = 0, n = objectOrArray.length; j < n; j = j + 1) {
|
1054
|
-
const value = objectOrArray[j];
|
1055
|
-
|
1056
|
-
if (value instanceof Object) {
|
1057
|
-
isShallow = false;
|
1058
|
-
break;
|
1059
|
-
}
|
1060
|
-
} // end for
|
1061
|
-
|
1062
|
-
if (isShallow) {
|
1063
|
-
// objectOrArray is already shallow -> nothing to do
|
1064
|
-
return objectOrArray;
|
1065
|
-
}
|
1066
|
-
|
1067
|
-
const outputArray = [];
|
1068
|
-
|
1069
|
-
for (let j = 0, n = objectOrArray.length; j < n; j = j + 1) {
|
1070
|
-
const value = objectOrArray[j];
|
1071
|
-
|
1072
|
-
if (!(value instanceof Object)) {
|
1073
|
-
outputArray.push(value);
|
1074
|
-
}
|
1075
|
-
// else {
|
1076
|
-
// //outputArray.push( null );
|
1077
|
-
// // outputArray.push( $removed );
|
1078
|
-
// }
|
1079
|
-
} // end for
|
1080
|
-
|
1081
|
-
return outputArray;
|
1082
|
-
} else {
|
1083
|
-
// obj is a not an array
|
1084
|
-
|
1085
|
-
let isShallow = true;
|
1086
|
-
|
1087
|
-
for (const key in objectOrArray) {
|
1088
|
-
const value = objectOrArray[key];
|
1089
|
-
|
1090
|
-
if (value instanceof Object) {
|
1091
|
-
isShallow = false;
|
1092
|
-
break;
|
1093
|
-
}
|
1094
|
-
// else {
|
1095
|
-
// // outputObj[ key ] = null;
|
1096
|
-
// // outputObj[ key ] = $removed;
|
1097
|
-
// }
|
1098
|
-
} // end for
|
1099
|
-
|
1100
|
-
if (isShallow) {
|
1101
|
-
// objectOrArray is already shallow -> nothing to do
|
1102
|
-
return objectOrArray;
|
1103
|
-
}
|
1104
|
-
|
1105
|
-
const outputObj = {};
|
1106
|
-
|
1107
|
-
for (const key in objectOrArray) {
|
1108
|
-
const value = objectOrArray[key];
|
1109
|
-
|
1110
|
-
if (!(value instanceof Object)) {
|
1111
|
-
outputObj[key] = value;
|
1112
|
-
}
|
1113
|
-
// else {
|
1114
|
-
// // outputObj[ key ] = null;
|
1115
|
-
// // outputObj[ key ] = $removed;
|
1116
|
-
// }
|
1117
|
-
} // end for
|
1118
|
-
|
1119
|
-
return outputObj;
|
1120
|
-
}
|
1121
|
-
}
|
1122
|
-
|
1123
|
-
// -----------------------------------------------------------------------------
|
1124
|
-
|
1125
|
-
/**
|
1126
|
-
* Update an object
|
1127
|
-
* - Sets the path-value pairs from the updateData in the object
|
1128
|
-
* - Existing values will be overwritten
|
1129
|
-
* - Existing intermediate values (objects, arrays) will be overwritten too
|
1130
|
-
* (if updateData is an object, not an iterable)
|
1131
|
-
*
|
1132
|
-
* @param {object} [obj] - Input object
|
1133
|
-
*
|
1134
|
-
* @param {object|iterable} updateData - Data to update
|
1135
|
-
*
|
1136
|
-
* Note that if using path-value pairs, the order of the pairs is relevant:
|
1137
|
-
* {
|
1138
|
-
* "some": {},
|
1139
|
-
* "some.path": {},
|
1140
|
-
* "some.path.to": 2,
|
1141
|
-
* }
|
1142
|
-
*
|
1143
|
-
* @returns {object} updated object
|
1144
|
-
*/
|
1145
|
-
export function updateObject(obj = null, updateData = null, options) {
|
1146
|
-
// -- Check parameter [obj]
|
1147
|
-
|
1148
|
-
expect.object(obj);
|
1149
|
-
|
1150
|
-
expect.object(updateData);
|
1151
|
-
|
1152
|
-
// -- Update cloned object
|
1153
|
-
|
1154
|
-
let pathValuePairs;
|
1155
|
-
|
1156
|
-
if (!isIterable(updateData)) {
|
1157
|
-
// Convert updateData to path-value pairs (iterable)
|
1158
|
-
|
1159
|
-
const walkArrays = options && options.replaceArrays ? true : false;
|
1160
|
-
|
1161
|
-
pathValuePairs = iterateObjectEntries(updateData, {
|
1162
|
-
expandPathKeys: true,
|
1163
|
-
outputIntermediateNodes: true,
|
1164
|
-
walkArrays
|
1165
|
-
});
|
1166
|
-
|
1167
|
-
// pathValuePairs = Array.from( pathValuePairs );
|
1168
|
-
// console.log( "CHECK", { updateData, pathValuePairs } );
|
1169
|
-
} else {
|
1170
|
-
// Iterable -> assume iterable of path-value pairs
|
1171
|
-
pathValuePairs = updateData;
|
1172
|
-
}
|
1173
|
-
|
1174
|
-
for (const [arrPath, value] of pathValuePairs) {
|
1175
|
-
// console.log( "updateObject:set", arrPath, value );
|
1176
|
-
objectSet(obj, arrPath, value);
|
1177
|
-
}
|
1178
|
-
|
1179
|
-
return obj;
|
1180
|
-
}
|
1181
|
-
|
1182
|
-
// -----------------------------------------------------------------------------
|
1183
|
-
|
1184
|
-
/**
|
1185
|
-
* Copy own properties from an object to another object if they do not
|
1186
|
-
* exist yet.
|
1187
|
-
*
|
1188
|
-
* @param {object} from - Object ot copy properties from
|
1189
|
-
* @param {object} to - Object to copy properties to
|
1190
|
-
*/
|
1191
|
-
export function copyOwnProperties(from, to) {
|
1192
|
-
const propertyNames = Object.getOwnPropertyNames(from);
|
1193
|
-
|
1194
|
-
const nProperties = propertyNames.length;
|
1195
|
-
|
1196
|
-
if (!nProperties) {
|
1197
|
-
return;
|
1198
|
-
}
|
1199
|
-
|
1200
|
-
const firstIsConstructor = 'constructor' === propertyNames[0] ? true : false;
|
1201
|
-
|
1202
|
-
if (1 === nProperties && firstIsConstructor) {
|
1203
|
-
return;
|
1204
|
-
}
|
1205
|
-
|
1206
|
-
const startAt = firstIsConstructor ? 1 : 0;
|
1207
|
-
|
1208
|
-
// console.log("propertyNames", from, propertyNames, startAt);
|
1209
|
-
|
1210
|
-
for (let j = startAt; j < nProperties; j = j + 1) {
|
1211
|
-
const key = propertyNames[j];
|
1212
|
-
|
1213
|
-
const descriptor = Object.getOwnPropertyDescriptor(from, key);
|
1214
|
-
|
1215
|
-
const targetDescriptor = Object.getOwnPropertyDescriptor(to, key);
|
1216
|
-
|
1217
|
-
// Not needed, we copy an instance
|
1218
|
-
// descriptor.value = clone( descriptor.value );
|
1219
|
-
|
1220
|
-
if (!targetDescriptor) {
|
1221
|
-
// Property does not yet exist
|
1222
|
-
Object.defineProperty(to, key, descriptor);
|
1223
|
-
}
|
1224
|
-
} // end for
|
1225
|
-
|
1226
|
-
// >>> FIXME? TODO? copy Symbols too? <<<
|
1227
|
-
}
|
1228
|
-
|
1229
|
-
// -----------------------------------------------------------------------------
|
1230
|
-
|
1231
|
-
/**
|
1232
|
-
* Convert string with dot separated values to a list of values
|
1233
|
-
* - Accepts that the supplied path is already an array path
|
1234
|
-
*
|
1235
|
-
* @param {string|string[]} path
|
1236
|
-
*
|
1237
|
-
* @returns {string[]} list of path values
|
1238
|
-
*/
|
1239
|
-
export function ensureArrayPath(path) {
|
1240
|
-
if (typeof path === 'string') {
|
1241
|
-
return path.split(PATH_SEPARATOR);
|
1242
|
-
} else if (Array.isArray(path)) {
|
1243
|
-
// Nothing to do
|
1244
|
-
return path;
|
1245
|
-
} else {
|
1246
|
-
throw new Error('Missing or invalid parameter [path] (expected string or array)');
|
1247
|
-
}
|
1248
|
-
}
|
1249
|
-
|
1250
|
-
/* ----------------------------------------------------- Internal methods */
|
1251
|
-
|
1252
|
-
// -----------------------------------------------------------------------------
|
1253
|
-
|
1254
|
-
/**
|
1255
|
-
* Create all parent objects on the object path if they do not yet exist yet
|
1256
|
-
* - This method will throw an exception if there is a non-object node in
|
1257
|
-
* the path
|
1258
|
-
*
|
1259
|
-
* @param {object} obj
|
1260
|
-
* Object to create the parent objects in
|
1261
|
-
*
|
1262
|
-
* @param {string[]} arrPath
|
1263
|
-
* The path that specified which parent oobjects to create
|
1264
|
-
*
|
1265
|
-
* @returns {object} the input object with the created object properties
|
1266
|
-
*/
|
1267
|
-
export function _ensureParent(obj, arrPath) {
|
1268
|
-
// console.log("_ensureParent (1)", { obj, arrPath } );
|
1269
|
-
|
1270
|
-
let current = obj;
|
1271
|
-
let prev = current;
|
1272
|
-
|
1273
|
-
for (let j = 0, n_1 = arrPath.length - 1; j < n_1; j = j + 1) {
|
1274
|
-
const key = arrPath[j];
|
1275
|
-
|
1276
|
-
current = current[key];
|
1277
|
-
|
1278
|
-
if (current === undefined || current === null) {
|
1279
|
-
current = prev[key] = {};
|
1280
|
-
prev = current;
|
1281
|
-
continue;
|
1282
|
-
}
|
1283
|
-
|
1284
|
-
const nextKey = arrPath[j + 1];
|
1285
|
-
|
1286
|
-
// Check
|
1287
|
-
// Check if current is an object
|
1288
|
-
// If current is an array, check if the nextKey can be set on a array
|
1289
|
-
|
1290
|
-
if (current instanceof Object) {
|
1291
|
-
if (Array.isArray(current) && j < n_1) {
|
1292
|
-
const nextKeyAsInt = parseInt(nextKey, 10);
|
1293
|
-
|
1294
|
-
if (Number.isNaN(nextKeyAsInt)) {
|
1295
|
-
// console.log("CHECK", { obj, arrPath, j, current, nextKey } );
|
1296
|
-
throw new Error(`Cannot set property [${nextKey}] ` + 'on data node of type [Array]');
|
1297
|
-
}
|
1298
|
-
}
|
1299
|
-
} else {
|
1300
|
-
// console.log( { current, prev, key, arrPath, j } );
|
1301
|
-
|
1302
|
-
throw new Error(
|
1303
|
-
`Cannot set property [${nextKey}] from ` +
|
1304
|
-
`path [${display_array_path(arrPath)}] on data node that is not ` +
|
1305
|
-
'an object or an array'
|
1306
|
-
);
|
1307
|
-
}
|
1308
|
-
|
1309
|
-
prev = current;
|
1310
|
-
} // end for
|
1311
|
-
|
1312
|
-
return current;
|
1313
|
-
}
|
1314
|
-
|
1315
|
-
// -----------------------------------------------------------------------------
|
1316
|
-
|
1317
|
-
/**
|
1318
|
-
* Get parent object at the specified path
|
1319
|
-
*
|
1320
|
-
* @param {object} obj - Object to work in
|
1321
|
-
* @param {string[]} arrPath - Path to get the parent object for
|
1322
|
-
*
|
1323
|
-
* @returns {object|array|null} parent object or null if not found
|
1324
|
-
*/
|
1325
|
-
export function _getParent(obj, arrPath) {
|
1326
|
-
let current = obj;
|
1327
|
-
|
1328
|
-
for (let j = 0, n_1 = arrPath.length - 1; j < n_1; j = j + 1) {
|
1329
|
-
let key = arrPath[j];
|
1330
|
-
|
1331
|
-
current = current[key];
|
1332
|
-
|
1333
|
-
if (typeof current === 'undefined') {
|
1334
|
-
return null;
|
1335
|
-
}
|
1336
|
-
|
1337
|
-
if (current instanceof Object) {
|
1338
|
-
if (Array.isArray(current) && j < n_1 - 1) {
|
1339
|
-
// node is an array
|
1340
|
-
// -> use next part of the array path as (numerical) array index
|
1341
|
-
|
1342
|
-
key = arrPath[j + 1];
|
1343
|
-
const keyAsInt = parseInt(key, 10);
|
1344
|
-
|
1345
|
-
if (Number.isNaN(keyAsInt)) {
|
1346
|
-
throw new Error(
|
1347
|
-
`Cannot get property [${display_array_path(arrPath, j)}]` +
|
1348
|
-
'from data node of type [Array]'
|
1349
|
-
);
|
1350
|
-
}
|
1351
|
-
}
|
1352
|
-
} else {
|
1353
|
-
throw new Error(
|
1354
|
-
`Cannot get property [${display_array_path(arrPath, j)}]` +
|
1355
|
-
'from a data node that is not an object or an array'
|
1356
|
-
);
|
1357
|
-
}
|
1358
|
-
} // end for
|
1359
|
-
|
1360
|
-
return current;
|
1361
|
-
}
|
1
|
+
/* ------------------------------------------------------------------ Imports */
|
2
|
+
|
3
|
+
import * as expect from '../expect/index.js';
|
4
|
+
|
5
|
+
import { equals } from '../compare/index.js';
|
6
|
+
|
7
|
+
import { toArrayPath } from '../array/index.js';
|
8
|
+
|
9
|
+
import { toStringPath } from '../string/index.js';
|
10
|
+
|
11
|
+
import { isIterable } from '../is/index.js';
|
12
|
+
|
13
|
+
import { iterateObjectPaths, iterateObjectEntries } from '../iterate/index.js';
|
14
|
+
|
15
|
+
// ------------------------------------------------------------------- Internals
|
16
|
+
|
17
|
+
const PATH_SEPARATOR = '.';
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Create a human friendly string representation of an array path
|
21
|
+
* - Allows for removal of the last part of the array part
|
22
|
+
*
|
23
|
+
* @param {string[]} arr - Array path to join
|
24
|
+
* @param {number} [lastIndex]
|
25
|
+
* If specified, only parts up and including the last index will
|
26
|
+
* be joined
|
27
|
+
*
|
28
|
+
* @returns {string} path as string
|
29
|
+
*/
|
30
|
+
function display_array_path(arr, lastIndex) {
|
31
|
+
return arr.slice(0, lastIndex).join(PATH_SEPARATOR);
|
32
|
+
}
|
33
|
+
|
34
|
+
const object_to_string = Object.prototype.toString;
|
35
|
+
const has_own_property = Object.prototype.hasOwnProperty;
|
36
|
+
|
37
|
+
/* ------------------------------------------------------------------ Exports */
|
38
|
+
|
39
|
+
export { PATH_SEPARATOR };
|
40
|
+
|
41
|
+
// -----------------------------------------------------------------------------
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Returns true
|
45
|
+
* - if the object has no (iterable) key value pairs
|
46
|
+
* - if the object is an empty array
|
47
|
+
*
|
48
|
+
* @param {object} obj
|
49
|
+
*
|
50
|
+
* @return {boolean}
|
51
|
+
* true if the object has no key value pairs or the supplied object
|
52
|
+
* is `falsey`
|
53
|
+
*/
|
54
|
+
export function isEmpty(obj) {
|
55
|
+
if (!obj) {
|
56
|
+
// object is null or other falsey value
|
57
|
+
return true;
|
58
|
+
}
|
59
|
+
|
60
|
+
expect.object(obj);
|
61
|
+
|
62
|
+
if (/*obj instanceof Array && */ 0 === obj.length) {
|
63
|
+
return true;
|
64
|
+
}
|
65
|
+
|
66
|
+
for (const key in obj) {
|
67
|
+
return false;
|
68
|
+
}
|
69
|
+
|
70
|
+
return true;
|
71
|
+
}
|
72
|
+
|
73
|
+
// -----------------------------------------------------------------------------
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Get the number of (iterable) key value pairs in the specified object
|
77
|
+
*
|
78
|
+
* @param {object} obj
|
79
|
+
*
|
80
|
+
* @return {number} number of iterable key value pairs
|
81
|
+
*/
|
82
|
+
export function objectSize(obj) {
|
83
|
+
expect.object(obj);
|
84
|
+
|
85
|
+
let count = 0;
|
86
|
+
|
87
|
+
// eslint-disable-next-line no-unused-vars
|
88
|
+
for (const _key in obj) {
|
89
|
+
count = count + 1;
|
90
|
+
}
|
91
|
+
|
92
|
+
return count;
|
93
|
+
}
|
94
|
+
|
95
|
+
// -----------------------------------------------------------------------------
|
96
|
+
|
97
|
+
/**
|
98
|
+
* Returns a shallow copy of the object without the properties that
|
99
|
+
* have the value [null] or [undefined]
|
100
|
+
*
|
101
|
+
* @param {object} obj
|
102
|
+
*
|
103
|
+
* @param {string[]} [onlyKeys]
|
104
|
+
* If specified, only the specified keys will be exported
|
105
|
+
*
|
106
|
+
* @returns {object} new object without the null properties
|
107
|
+
*/
|
108
|
+
export function exportNotNull(obj, onlyKeys) {
|
109
|
+
expect.object(obj);
|
110
|
+
|
111
|
+
// if( onlyKeys )
|
112
|
+
// {
|
113
|
+
// expect.array( onlyKeys );
|
114
|
+
// }
|
115
|
+
|
116
|
+
const newObj = {};
|
117
|
+
|
118
|
+
const onlyKeysSet = onlyKeys ? new Set(onlyKeys) : null;
|
119
|
+
|
120
|
+
for (const key in obj) {
|
121
|
+
const value = obj[key];
|
122
|
+
|
123
|
+
if (value !== null && value !== undefined) {
|
124
|
+
if (onlyKeysSet && !onlyKeysSet.has(key)) {
|
125
|
+
continue;
|
126
|
+
}
|
127
|
+
|
128
|
+
newObj[key] = value;
|
129
|
+
}
|
130
|
+
} // end for
|
131
|
+
|
132
|
+
return newObj;
|
133
|
+
}
|
134
|
+
|
135
|
+
// -----------------------------------------------------------------------------
|
136
|
+
|
137
|
+
/**
|
138
|
+
* Returns a shallow copy of the object without the properties that
|
139
|
+
* are `private`
|
140
|
+
* - Private properties are properties that start with an underscore
|
141
|
+
* `_`.
|
142
|
+
*
|
143
|
+
* @param {object} obj
|
144
|
+
*
|
145
|
+
* @param {string[]} [keepKeys]
|
146
|
+
* If specified, the sprecified private keys will be exported (e.g. `_id`)
|
147
|
+
*
|
148
|
+
* @returns {object} new object without the null properties
|
149
|
+
*/
|
150
|
+
export function exportNotPrivate(obj, keepKeys) {
|
151
|
+
expect.object(obj);
|
152
|
+
|
153
|
+
const newObj = {};
|
154
|
+
|
155
|
+
const keepKeysSet = keepKeys ? new Set(keepKeys) : null;
|
156
|
+
|
157
|
+
for (const key in obj) {
|
158
|
+
const value = obj[key];
|
159
|
+
|
160
|
+
if (!key.startsWith('_')) {
|
161
|
+
newObj[key] = value;
|
162
|
+
} else if (keepKeysSet && keepKeysSet.has(key)) {
|
163
|
+
//
|
164
|
+
// Add key to keep as read only property
|
165
|
+
//
|
166
|
+
Object.defineProperty(newObj, key, {
|
167
|
+
value,
|
168
|
+
writable: false,
|
169
|
+
enumerable: true
|
170
|
+
});
|
171
|
+
}
|
172
|
+
} // end for
|
173
|
+
|
174
|
+
return newObj;
|
175
|
+
}
|
176
|
+
|
177
|
+
// -----------------------------------------------------------------------------
|
178
|
+
|
179
|
+
// export function removeNull()
|
180
|
+
|
181
|
+
// -----------------------------------------------------------------------------
|
182
|
+
|
183
|
+
/**
|
184
|
+
* Keep only the specified keys in the object
|
185
|
+
* - deletes all other key-value pairs in the object
|
186
|
+
*
|
187
|
+
* @param {object} obj
|
188
|
+
* @param {string[]|Set} keys
|
189
|
+
* @param {boolean} [removeNullAndUndefined=true]
|
190
|
+
*
|
191
|
+
* @returns {object} object that only contains the specified keys
|
192
|
+
*/
|
193
|
+
export function keep(obj, keys, removeNullAndUndefined = true) {
|
194
|
+
expect.object(obj);
|
195
|
+
expect.arrayOrSet(keys);
|
196
|
+
|
197
|
+
const keep = keys instanceof Set ? keys : new Set(keys);
|
198
|
+
|
199
|
+
for (const key in obj) {
|
200
|
+
if (!keep.has(key)) {
|
201
|
+
delete obj[key];
|
202
|
+
continue;
|
203
|
+
}
|
204
|
+
|
205
|
+
const value = obj[key];
|
206
|
+
|
207
|
+
if (removeNullAndUndefined) {
|
208
|
+
if (value === null || value === undefined) {
|
209
|
+
delete obj[key];
|
210
|
+
}
|
211
|
+
}
|
212
|
+
} // end for
|
213
|
+
|
214
|
+
return obj;
|
215
|
+
}
|
216
|
+
|
217
|
+
// -----------------------------------------------------------------------------
|
218
|
+
|
219
|
+
/**
|
220
|
+
* Freezes an object recursively
|
221
|
+
* - Allows non-objects to be passed as input parameter (non-objects are
|
222
|
+
* immutable by default).
|
223
|
+
*
|
224
|
+
* @param {any} value
|
225
|
+
*
|
226
|
+
* @returns {any}
|
227
|
+
* recursively frozen object or original input value if a non-object was
|
228
|
+
* supplied as input parameter
|
229
|
+
*/
|
230
|
+
export function deepFreeze(value, _found) {
|
231
|
+
if (!(value instanceof Object)) {
|
232
|
+
return value;
|
233
|
+
}
|
234
|
+
|
235
|
+
if (!_found) {
|
236
|
+
_found = new Set();
|
237
|
+
} else if (_found.has(value)) {
|
238
|
+
// Using recursion -> no need to return value
|
239
|
+
return;
|
240
|
+
}
|
241
|
+
|
242
|
+
_found.add(value);
|
243
|
+
|
244
|
+
Object.freeze(value);
|
245
|
+
|
246
|
+
for (const key in value) {
|
247
|
+
const childObj = value[key];
|
248
|
+
|
249
|
+
if (childObj instanceof Object) {
|
250
|
+
// Recurse into child objects
|
251
|
+
deepFreeze(childObj, _found);
|
252
|
+
}
|
253
|
+
} // end for
|
254
|
+
|
255
|
+
return value;
|
256
|
+
}
|
257
|
+
|
258
|
+
// -----------------------------------------------------------------------------
|
259
|
+
|
260
|
+
/**
|
261
|
+
* Set a value in an object using a path and value pair.
|
262
|
+
* - Automatically creates parent objects
|
263
|
+
*
|
264
|
+
* @param {object} obj - Object to set the value in
|
265
|
+
* @param {string|Array} path - Dot separated string path or array path
|
266
|
+
* @param {any} value - value to set
|
267
|
+
*
|
268
|
+
* @returns {boolean} true if the value was changed
|
269
|
+
*/
|
270
|
+
export function objectSet(obj, path, value) {
|
271
|
+
expect.object(obj);
|
272
|
+
|
273
|
+
const arrPath = toArrayPath(path);
|
274
|
+
|
275
|
+
if (arguments.length < 3) {
|
276
|
+
throw new Error('Missing or invalid parameter [value]');
|
277
|
+
}
|
278
|
+
|
279
|
+
let parentNode;
|
280
|
+
const lastKey = arrPath[arrPath.length - 1];
|
281
|
+
|
282
|
+
if (value !== undefined) {
|
283
|
+
parentNode = _ensureParent(obj, arrPath);
|
284
|
+
} else {
|
285
|
+
//
|
286
|
+
// value is undefined -> delete node
|
287
|
+
//
|
288
|
+
parentNode = _getParent(obj, arrPath);
|
289
|
+
|
290
|
+
if (Array.isArray(parentNode)) {
|
291
|
+
const keyAsInt = parseInt(lastKey, 10);
|
292
|
+
|
293
|
+
if (Number.isNaN(keyAsInt)) {
|
294
|
+
throw new Error(
|
295
|
+
'Cannot delete property [' +
|
296
|
+
arrPath.join(PATH_SEPARATOR) +
|
297
|
+
'] ' +
|
298
|
+
'from data node of type [Array]'
|
299
|
+
);
|
300
|
+
}
|
301
|
+
|
302
|
+
if (keyAsInt < parentNode.length) {
|
303
|
+
parentNode.splice(keyAsInt, 1);
|
304
|
+
return true;
|
305
|
+
}
|
306
|
+
|
307
|
+
return false;
|
308
|
+
} else if (parentNode) {
|
309
|
+
if (lastKey in parentNode) {
|
310
|
+
delete parentNode[lastKey];
|
311
|
+
return true;
|
312
|
+
}
|
313
|
+
return false;
|
314
|
+
}
|
315
|
+
}
|
316
|
+
|
317
|
+
// -- Set value
|
318
|
+
|
319
|
+
const existingValue = parentNode[lastKey];
|
320
|
+
|
321
|
+
if (!equals(value, existingValue)) {
|
322
|
+
parentNode[lastKey] = value;
|
323
|
+
|
324
|
+
return true;
|
325
|
+
}
|
326
|
+
|
327
|
+
return false;
|
328
|
+
}
|
329
|
+
|
330
|
+
// -----------------------------------------------------------------------------
|
331
|
+
|
332
|
+
/**
|
333
|
+
* Removes a value at the specified object path from the object.
|
334
|
+
* - All parent objects that remain empty will be removed too (recursively)
|
335
|
+
*
|
336
|
+
* @param {object} obj - Object to set the value in
|
337
|
+
* @param {string|Array} path - Dot separated string path or array path
|
338
|
+
* @param {any} value - value to set
|
339
|
+
*/
|
340
|
+
export function deletePath(obj, path) {
|
341
|
+
expect.object(obj);
|
342
|
+
|
343
|
+
const arrPath = toArrayPath(path);
|
344
|
+
|
345
|
+
const n = arrPath.length;
|
346
|
+
const n_1 = n - 1;
|
347
|
+
|
348
|
+
if (!n) {
|
349
|
+
// Path is empty ""
|
350
|
+
return;
|
351
|
+
}
|
352
|
+
|
353
|
+
const lastKey = arrPath[n_1];
|
354
|
+
|
355
|
+
if (1 === n) {
|
356
|
+
// Path consist of a single key
|
357
|
+
delete obj[lastKey];
|
358
|
+
return;
|
359
|
+
}
|
360
|
+
|
361
|
+
// path is longer than a single key >>
|
362
|
+
|
363
|
+
// -- Get parent objects
|
364
|
+
|
365
|
+
const parents = [];
|
366
|
+
|
367
|
+
let current = obj;
|
368
|
+
|
369
|
+
let endValueFound = true;
|
370
|
+
|
371
|
+
for (let j = 0; j < n; j = j + 1) {
|
372
|
+
if (!(current instanceof Object)) {
|
373
|
+
break;
|
374
|
+
}
|
375
|
+
|
376
|
+
parents.push(current);
|
377
|
+
|
378
|
+
const key = arrPath[j];
|
379
|
+
|
380
|
+
// console.log(
|
381
|
+
// {
|
382
|
+
// current,
|
383
|
+
// key,
|
384
|
+
// next: current[ key ]
|
385
|
+
// } );
|
386
|
+
|
387
|
+
if (!(key in current)) {
|
388
|
+
// child not found -> no more parents
|
389
|
+
endValueFound = false;
|
390
|
+
break;
|
391
|
+
}
|
392
|
+
|
393
|
+
current = current[key];
|
394
|
+
}
|
395
|
+
|
396
|
+
// console.log( "parents", parents );
|
397
|
+
|
398
|
+
// -- Delete value from direct parent
|
399
|
+
|
400
|
+
const n_parents = parents.length - 1;
|
401
|
+
|
402
|
+
if (endValueFound) {
|
403
|
+
const lastParent = parents[n_parents];
|
404
|
+
|
405
|
+
if (!Array.isArray(lastParent)) {
|
406
|
+
delete lastParent[lastKey];
|
407
|
+
} else {
|
408
|
+
lastParent.splice(parseInt(lastKey, 10), 1);
|
409
|
+
}
|
410
|
+
}
|
411
|
+
|
412
|
+
// -- Remove empty parents
|
413
|
+
|
414
|
+
for (let j = n_parents - 1; j >= 0; j = j - 1) {
|
415
|
+
const parent = parents[j];
|
416
|
+
const key = arrPath[j];
|
417
|
+
const child = parent[key];
|
418
|
+
|
419
|
+
let childIsEmpty = false;
|
420
|
+
|
421
|
+
if (Array.isArray(child)) {
|
422
|
+
// Child is array
|
423
|
+
if (0 === child.length) {
|
424
|
+
childIsEmpty = true;
|
425
|
+
}
|
426
|
+
} else {
|
427
|
+
// Child is object
|
428
|
+
if (0 === Object.keys(child).length) {
|
429
|
+
childIsEmpty = true;
|
430
|
+
}
|
431
|
+
}
|
432
|
+
|
433
|
+
if (!childIsEmpty) {
|
434
|
+
// done
|
435
|
+
break;
|
436
|
+
}
|
437
|
+
|
438
|
+
// Remove empty child from parent
|
439
|
+
|
440
|
+
if (!Array.isArray(parent)) {
|
441
|
+
delete parent[key];
|
442
|
+
break;
|
443
|
+
} else {
|
444
|
+
parent.splice(parseInt(key, 10), 1);
|
445
|
+
}
|
446
|
+
} // end for
|
447
|
+
}
|
448
|
+
|
449
|
+
// -----------------------------------------------------------------------------
|
450
|
+
|
451
|
+
/**
|
452
|
+
* Get a value from an object using a path
|
453
|
+
* - Returns a default value if not found, with is [undefined] by default
|
454
|
+
*
|
455
|
+
* @param {object} obj - Object to get the value from
|
456
|
+
* @param {string|Array} path - Dot separated string path or array path
|
457
|
+
*
|
458
|
+
* @param {any} [defaultValue=undefined]
|
459
|
+
* Value to return if the value does not exist
|
460
|
+
*
|
461
|
+
* @return {any} value found at path, defaultValue or undefined
|
462
|
+
*/
|
463
|
+
export function objectGet(obj, path, defaultValue) {
|
464
|
+
expect.object(obj);
|
465
|
+
|
466
|
+
const arrPath = toArrayPath(path);
|
467
|
+
|
468
|
+
if (!path.length || (1 === path.length && !path[0].length)) {
|
469
|
+
// "" or [""]
|
470
|
+
return obj;
|
471
|
+
}
|
472
|
+
|
473
|
+
const parentNode = _getParent(obj, arrPath);
|
474
|
+
|
475
|
+
if (!parentNode) {
|
476
|
+
return defaultValue; // @note may be undefined
|
477
|
+
}
|
478
|
+
|
479
|
+
const lastKey = arrPath[arrPath.length - 1];
|
480
|
+
|
481
|
+
const value = parentNode[lastKey];
|
482
|
+
|
483
|
+
if (value === undefined) {
|
484
|
+
return defaultValue; // @note may be undefined
|
485
|
+
}
|
486
|
+
|
487
|
+
return value;
|
488
|
+
}
|
489
|
+
|
490
|
+
// -----------------------------------------------------------------------------
|
491
|
+
|
492
|
+
/**
|
493
|
+
* Get a value from an object using a path
|
494
|
+
* - Throws an exception if the path does not exist or the value is undefined
|
495
|
+
*
|
496
|
+
* @param {object} obj - Object to get the value from
|
497
|
+
* @param {string|Array} path - Dot separated string path or array path
|
498
|
+
*
|
499
|
+
* @param {function} [parseFn]
|
500
|
+
* Optional parser function that checks and converts the value
|
501
|
+
*
|
502
|
+
* @throws No value found at path
|
503
|
+
* @throws Invalid value
|
504
|
+
*
|
505
|
+
* @return {any} value found at path
|
506
|
+
*/
|
507
|
+
export function objectGetWithThrow(obj, path, parseFn) {
|
508
|
+
let value = objectGet(obj, path);
|
509
|
+
|
510
|
+
if (parseFn) {
|
511
|
+
const { value: parsedValue, error } = parseFn(value);
|
512
|
+
|
513
|
+
if (error) {
|
514
|
+
throw new Error(`Invalid value found at path [${toStringPath(path)}]`, { cause: error });
|
515
|
+
}
|
516
|
+
|
517
|
+
value = parsedValue;
|
518
|
+
}
|
519
|
+
|
520
|
+
if (value === undefined) {
|
521
|
+
throw new Error(`No value found at path [${toStringPath(path)}]`);
|
522
|
+
}
|
523
|
+
|
524
|
+
return value;
|
525
|
+
}
|
526
|
+
|
527
|
+
// -----------------------------------------------------------------------------
|
528
|
+
|
529
|
+
// DEV >>>>
|
530
|
+
|
531
|
+
/**
|
532
|
+
* Get an iterator that returns the value of a path for each item (object)
|
533
|
+
* in the list of objects
|
534
|
+
*
|
535
|
+
* @param {object[]} arr - Array of objects
|
536
|
+
* @param {string|string[]} path - Dot separated string path or array path
|
537
|
+
*
|
538
|
+
* @param {object} [options] - options
|
539
|
+
*
|
540
|
+
* DEPRECEATED >>> NOT COMPATIBLE WITH LIGHTWEIGHT ITERATOR
|
541
|
+
* @param {object} [options.unique=false] - Only return unique values
|
542
|
+
*
|
543
|
+
* @param {any} [options.defaultValue]
|
544
|
+
* Value to return if the value does not exist
|
545
|
+
*
|
546
|
+
* @returns {Iterator<mixed>} value at the specified path for each item
|
547
|
+
*/
|
548
|
+
// export function values( arr, path, options )
|
549
|
+
// {
|
550
|
+
// if( !Array.isArray(arr) )
|
551
|
+
// {
|
552
|
+
// throw new Error("Missing or invalid parameter [arr] (expected Array)");
|
553
|
+
// }
|
554
|
+
|
555
|
+
// if( typeof path !== "string" && !Array.isArray(path) )
|
556
|
+
// {
|
557
|
+
// throw new Error(
|
558
|
+
// "Missing or invalid parameter [path] (expected string or Array");
|
559
|
+
// }
|
560
|
+
|
561
|
+
// options = Object.assign(
|
562
|
+
// {
|
563
|
+
// unique: false,
|
564
|
+
// defaultValue: undefined
|
565
|
+
// },
|
566
|
+
// options );
|
567
|
+
|
568
|
+
// if( options.unique )
|
569
|
+
// {
|
570
|
+
// // Keep track of all values to prevent duplicates
|
571
|
+
// }
|
572
|
+
|
573
|
+
// throw new Error("NOT IMPLEMENTED YET");
|
574
|
+
// }
|
575
|
+
|
576
|
+
// <<< DEV
|
577
|
+
|
578
|
+
// -----------------------------------------------------------------------------
|
579
|
+
|
580
|
+
/**
|
581
|
+
* Returns a list of differences between the object before and the object
|
582
|
+
* after the changes.
|
583
|
+
* - By default, the function returns changes for added, updated and removed
|
584
|
+
* properties
|
585
|
+
*
|
586
|
+
* @param {object} objBefore
|
587
|
+
* @param {object} objAfter
|
588
|
+
*
|
589
|
+
* @param {object} options
|
590
|
+
* @param {boolean} [options.ignoreAdd=false]
|
591
|
+
* @param {boolean} [options.ignoreUpdate=false]
|
592
|
+
* @param {boolean} [options.ignoreDelete=false]
|
593
|
+
*
|
594
|
+
* @param {boolean} [options.ignorePrivate=true]
|
595
|
+
* Ignore properties that start with an underscore e.g. _id or _updatedAt
|
596
|
+
*
|
597
|
+
* @param {boolean} [options.deleteValue=null]
|
598
|
+
*
|
599
|
+
* @returns {array}
|
600
|
+
* List of changes between the object before and object after
|
601
|
+
*/
|
602
|
+
export function objectDiff(objBefore, objAfter, options = {}, _recursion) {
|
603
|
+
const changes = [];
|
604
|
+
|
605
|
+
const ignoreAdd = undefined === options.ignoreAdd ? false : options.ignoreAdd;
|
606
|
+
|
607
|
+
const ignoreUpdate = undefined === options.ignoreUpdate ? false : options.ignoreUpdate;
|
608
|
+
|
609
|
+
const ignoreDelete = undefined === options.ignoreDelete ? false : options.ignoreDelete;
|
610
|
+
|
611
|
+
const ignorePrivate = undefined === options.ignorePrivate ? true : options.ignorePrivate;
|
612
|
+
|
613
|
+
let deleteValue = null;
|
614
|
+
|
615
|
+
if ('deletevalue' in options) {
|
616
|
+
// Might be [undefined]
|
617
|
+
deleteValue = options.deleteValue;
|
618
|
+
}
|
619
|
+
|
620
|
+
objBefore = objBefore || {};
|
621
|
+
|
622
|
+
for (const key in objAfter) {
|
623
|
+
if (ignorePrivate && key.startsWith('_')) {
|
624
|
+
continue;
|
625
|
+
}
|
626
|
+
|
627
|
+
const newValue = objAfter[key];
|
628
|
+
|
629
|
+
if (deleteValue !== newValue) {
|
630
|
+
const previousValue = objBefore[key];
|
631
|
+
|
632
|
+
if (newValue === previousValue) {
|
633
|
+
// No change
|
634
|
+
continue;
|
635
|
+
}
|
636
|
+
|
637
|
+
if (previousValue instanceof Object && newValue instanceof Object) {
|
638
|
+
// TODO: _recursion
|
639
|
+
|
640
|
+
const diff = objectDiff(newValue, previousValue, options, _recursion);
|
641
|
+
|
642
|
+
if (0 === diff.length) {
|
643
|
+
// No change
|
644
|
+
continue;
|
645
|
+
}
|
646
|
+
}
|
647
|
+
|
648
|
+
if (undefined !== previousValue) {
|
649
|
+
if (!ignoreUpdate) {
|
650
|
+
// update property value
|
651
|
+
changes.push({ path: key, set: newValue });
|
652
|
+
}
|
653
|
+
} else if (!ignoreAdd) {
|
654
|
+
// add property
|
655
|
+
changes.push({ path: key, set: newValue });
|
656
|
+
}
|
657
|
+
} else if (!ignoreDelete) {
|
658
|
+
// newValue === deleteValue && ignoreDelete=true
|
659
|
+
changes.push({ path: key, unset: 1 });
|
660
|
+
}
|
661
|
+
} // end for
|
662
|
+
|
663
|
+
return changes;
|
664
|
+
}
|
665
|
+
|
666
|
+
// -----------------------------------------------------------------------------
|
667
|
+
|
668
|
+
/**
|
669
|
+
* Applies a list of differences to the input object
|
670
|
+
* - A list of changes can be generated by e.g. objectDiff
|
671
|
+
*
|
672
|
+
* @param {object} obj
|
673
|
+
* @param {object[]} changes
|
674
|
+
*
|
675
|
+
* @param {object} options
|
676
|
+
* @param {boolean} [options.ignoreAdd=false]
|
677
|
+
* @param {boolean} [options.ignoreUpdate=false]
|
678
|
+
* @param {boolean} [options.ignoreDelete=false]
|
679
|
+
*
|
680
|
+
* @param {boolean} [options.ignorePrivate=true]
|
681
|
+
* Ignore properties that start with an underscore e.g. _id or _updatedAt
|
682
|
+
*/
|
683
|
+
export function patchObject(obj, changes, options = {}) {
|
684
|
+
expect.object(obj);
|
685
|
+
expect.array(changes);
|
686
|
+
|
687
|
+
const ignoreAdd = undefined === options.ignoreAdd ? false : options.ignoreAdd;
|
688
|
+
|
689
|
+
const ignoreUpdate = undefined === options.ignoreUpdate ? false : options.ignoreUpdate;
|
690
|
+
|
691
|
+
const ignoreDelete = undefined === options.ignoreDelete ? false : options.ignoreDelete;
|
692
|
+
|
693
|
+
const ignorePrivate = undefined === options.ignorePrivate ? true : options.ignorePrivate;
|
694
|
+
|
695
|
+
for (let j = 0, n = changes.length; j < n; j = j + 1) {
|
696
|
+
const change = changes[j];
|
697
|
+
|
698
|
+
const path = change.path;
|
699
|
+
|
700
|
+
// FIXME: recursion
|
701
|
+
|
702
|
+
if (ignorePrivate && path.startsWith('_')) {
|
703
|
+
continue;
|
704
|
+
}
|
705
|
+
|
706
|
+
if ('unset' in change) {
|
707
|
+
if (ignoreDelete) {
|
708
|
+
continue;
|
709
|
+
}
|
710
|
+
|
711
|
+
// console.log( "DELETE", change );
|
712
|
+
objectSet(obj, path, undefined); // undefined deletes property
|
713
|
+
continue;
|
714
|
+
}
|
715
|
+
|
716
|
+
const newValue = change.set;
|
717
|
+
|
718
|
+
if (undefined === newValue) {
|
719
|
+
//logError("Invalid [change]", ignoreDelete, change);
|
720
|
+
throw new Error(`Cannot set value [${path}=undefined]`);
|
721
|
+
}
|
722
|
+
|
723
|
+
if (ignoreAdd && Object._get(obj, path) === undefined) {
|
724
|
+
// Ignore add
|
725
|
+
continue;
|
726
|
+
} else if (ignoreUpdate && objectGet(obj, path) !== undefined) {
|
727
|
+
// Ignore update
|
728
|
+
continue;
|
729
|
+
}
|
730
|
+
|
731
|
+
// Set new value
|
732
|
+
objectSet(obj, path, newValue);
|
733
|
+
} // end for
|
734
|
+
}
|
735
|
+
|
736
|
+
// -----------------------------------------------------------------------------
|
737
|
+
|
738
|
+
/**
|
739
|
+
* Extend the target object with methods and properties from the source
|
740
|
+
* property object
|
741
|
+
* - The target object will be extended by inserting the source property
|
742
|
+
* object into it's property chain
|
743
|
+
|
744
|
+
* - If the current target's prototype is not the same as the source
|
745
|
+
* prototype, the source prototype will be cloned
|
746
|
+
*
|
747
|
+
* @param {object} target - Target object
|
748
|
+
* @param {object} source
|
749
|
+
* object to append to the target's prototype chain. The object and it's
|
750
|
+
* prototype objects are cloned first
|
751
|
+
*/
|
752
|
+
export function extend(target, source) {
|
753
|
+
expect.objectNoFunction(target);
|
754
|
+
|
755
|
+
if (Object.isFrozen(target)) {
|
756
|
+
throw new Error('Invalid parameter [target] (object is immutable)');
|
757
|
+
}
|
758
|
+
|
759
|
+
expect.objectNoFunction(source);
|
760
|
+
|
761
|
+
// let sourceProto = source.prototype;
|
762
|
+
|
763
|
+
let targetProto = Object.getPrototypeOf(target);
|
764
|
+
|
765
|
+
if (targetProto === Object.prototype || Object.isFrozen(targetProto)) {
|
766
|
+
// FIXME: || Object.isFrozen(targetProto) should not be necessary!
|
767
|
+
|
768
|
+
targetProto = target;
|
769
|
+
}
|
770
|
+
|
771
|
+
{
|
772
|
+
let obj = source;
|
773
|
+
|
774
|
+
while (obj && obj !== Object.prototype) {
|
775
|
+
const next = Object.getPrototypeOf(obj);
|
776
|
+
|
777
|
+
copyOwnProperties(obj, targetProto);
|
778
|
+
|
779
|
+
obj = next;
|
780
|
+
}
|
781
|
+
}
|
782
|
+
|
783
|
+
return;
|
784
|
+
}
|
785
|
+
|
786
|
+
// -----------------------------------------------------------------------------
|
787
|
+
|
788
|
+
/**
|
789
|
+
* Get a list of property names of the specified object
|
790
|
+
*
|
791
|
+
* @param {object} obj
|
792
|
+
*
|
793
|
+
* @returns {string[]} List of property names
|
794
|
+
*/
|
795
|
+
export function getPrototypeNames(obj) {
|
796
|
+
expect.object(obj);
|
797
|
+
|
798
|
+
let proto = obj.prototype || obj;
|
799
|
+
|
800
|
+
const names = [];
|
801
|
+
|
802
|
+
while (proto) {
|
803
|
+
// console.log( proto );
|
804
|
+
|
805
|
+
names.push(proto.constructor.name);
|
806
|
+
|
807
|
+
proto = Object.getPrototypeOf(proto);
|
808
|
+
}
|
809
|
+
|
810
|
+
return names;
|
811
|
+
}
|
812
|
+
|
813
|
+
// -----------------------------------------------------------------------------
|
814
|
+
|
815
|
+
/**
|
816
|
+
* Get a tree of values from an object
|
817
|
+
* - Can returns default values if one or multiple values were not found
|
818
|
+
*
|
819
|
+
* @param {object} obj - Object to get the value from
|
820
|
+
* @param {object} tree
|
821
|
+
* Tree that contains the object paths to get.
|
822
|
+
*
|
823
|
+
* e.g. { path: 1 }
|
824
|
+
* { some: { path: { to: 1 }, otherPath: { to: 1 } } }
|
825
|
+
* { "some.path.to": 1, "some.otherPath.to": 1 }
|
826
|
+
* { someArray.0.name: 1 }
|
827
|
+
*
|
828
|
+
* @param {object} [options]
|
829
|
+
* @param {object} [options.shallowLeaves=false]
|
830
|
+
* If set to true, the values of the leaves of the tree will be converted
|
831
|
+
* to shallow objects if the leaves are nested objects.
|
832
|
+
*
|
833
|
+
* @return {object}
|
834
|
+
* nested object with the values that were found or defaultValues
|
835
|
+
*/
|
836
|
+
export function getTree(obj, tree, options) {
|
837
|
+
expect.object(obj);
|
838
|
+
expect.object(tree);
|
839
|
+
|
840
|
+
let shallowLeaves = false;
|
841
|
+
|
842
|
+
if (options instanceof Object && options.shallowLeaves) {
|
843
|
+
shallowLeaves = true;
|
844
|
+
}
|
845
|
+
|
846
|
+
const it = iterateObjectPaths(tree, { walkArrays: true });
|
847
|
+
|
848
|
+
const result = {};
|
849
|
+
|
850
|
+
for (let arrPath of it) {
|
851
|
+
// console.log( { arrPath } );
|
852
|
+
|
853
|
+
if (1 === arrPath.length && arrPath[0].includes(PATH_SEPARATOR)) {
|
854
|
+
// Convert "short path syntax" to array path
|
855
|
+
arrPath = arrPath[0].split(PATH_SEPARATOR);
|
856
|
+
}
|
857
|
+
|
858
|
+
// -- Get value from object at current path
|
859
|
+
|
860
|
+
const leaveValue = objectGet(obj, arrPath);
|
861
|
+
|
862
|
+
// -- Set value in result object at current path
|
863
|
+
|
864
|
+
if (!shallowLeaves || !(leaveValue instanceof Object)) {
|
865
|
+
// option: shallowLeaves=false OR value is not an object
|
866
|
+
// -> no need to convert leaves to shallow objects
|
867
|
+
objectSet(result, arrPath, leaveValue);
|
868
|
+
} else {
|
869
|
+
// Set shallow leave value instead of nested object
|
870
|
+
|
871
|
+
const shallowLeaveValue = shallowClone(leaveValue);
|
872
|
+
objectSet(result, arrPath, shallowLeaveValue);
|
873
|
+
}
|
874
|
+
} // end for
|
875
|
+
|
876
|
+
return result;
|
877
|
+
}
|
878
|
+
|
879
|
+
// -----------------------------------------------------------------------------
|
880
|
+
|
881
|
+
/**
|
882
|
+
* Deep clone an object or any kind of other variable
|
883
|
+
* - Recursively clone all nested properties
|
884
|
+
* - Properties in the prototype chain are cloned too, but all copied into a
|
885
|
+
* single prototype object
|
886
|
+
* - This method works on objects, but also on any other JS variable type
|
887
|
+
* - If a value cannot be cloned, a reference is returned. o.a. for
|
888
|
+
* - Error objects
|
889
|
+
* - Browser objects
|
890
|
+
* - Functions
|
891
|
+
*
|
892
|
+
* @param {any} objectToBeCloned - Variable to clone
|
893
|
+
*
|
894
|
+
* @returns {any} cloned output
|
895
|
+
*/
|
896
|
+
export function clone(objectToBeCloned, _seenObjects) {
|
897
|
+
// const startTime = Date.now();
|
898
|
+
|
899
|
+
// -- Return references for all variables that are not objects
|
900
|
+
|
901
|
+
if (!(objectToBeCloned instanceof Object)) {
|
902
|
+
return objectToBeCloned;
|
903
|
+
}
|
904
|
+
|
905
|
+
// --- Check if variable has already been cloned before
|
906
|
+
|
907
|
+
if (!_seenObjects) {
|
908
|
+
_seenObjects = { originals: [], clones: [] };
|
909
|
+
} else {
|
910
|
+
const originals = _seenObjects.originals;
|
911
|
+
|
912
|
+
if (originals.length > 0) {
|
913
|
+
const foundIndex = originals.indexOf(objectToBeCloned);
|
914
|
+
|
915
|
+
if (-1 !== foundIndex) {
|
916
|
+
//console.log("(recursion) return
|
917
|
+
//from [_seenObjects]", _seenObjects.clones, foundIndex);
|
918
|
+
return _seenObjects.clones[foundIndex];
|
919
|
+
}
|
920
|
+
}
|
921
|
+
}
|
922
|
+
|
923
|
+
// -- Handle array (like) objects
|
924
|
+
|
925
|
+
const typeString = object_to_string.call(objectToBeCloned);
|
926
|
+
|
927
|
+
if (Array.isArray(objectToBeCloned) || '[object Arguments]' === typeString) {
|
928
|
+
const objectClone = [];
|
929
|
+
|
930
|
+
for (let j = 0, n = objectToBeCloned.length; j < n; j = j + 1) {
|
931
|
+
objectClone[j] = clone(objectToBeCloned[j], _seenObjects);
|
932
|
+
}
|
933
|
+
|
934
|
+
_seenObjects.originals.push(objectToBeCloned);
|
935
|
+
_seenObjects.clones.push(objectClone);
|
936
|
+
|
937
|
+
return objectClone;
|
938
|
+
}
|
939
|
+
|
940
|
+
// -- Handle not clonable objects
|
941
|
+
|
942
|
+
if ('[object Object]' !== typeString || objectToBeCloned instanceof Error) {
|
943
|
+
// Functions, Browser objects, ...
|
944
|
+
// - Not clonable -> return reference
|
945
|
+
// - No need to add to _seenObjects
|
946
|
+
return objectToBeCloned;
|
947
|
+
}
|
948
|
+
|
949
|
+
// -- Handle special objects
|
950
|
+
{
|
951
|
+
// Filter out special objects.
|
952
|
+
const Constructor = objectToBeCloned.constructor;
|
953
|
+
|
954
|
+
let objectClone;
|
955
|
+
|
956
|
+
switch (Constructor) {
|
957
|
+
case RegExp:
|
958
|
+
objectClone = new Constructor(objectToBeCloned);
|
959
|
+
break;
|
960
|
+
|
961
|
+
case Date:
|
962
|
+
objectClone = new Constructor(objectToBeCloned.getTime());
|
963
|
+
break;
|
964
|
+
|
965
|
+
// TODO: other special objects ...
|
966
|
+
}
|
967
|
+
|
968
|
+
if (objectClone) {
|
969
|
+
_seenObjects.originals.push(objectToBeCloned);
|
970
|
+
_seenObjects.clones.push(objectClone);
|
971
|
+
|
972
|
+
return objectClone;
|
973
|
+
}
|
974
|
+
|
975
|
+
if (objectToBeCloned.hkDoNotClone) {
|
976
|
+
// Not clonable flag was set -> return reference
|
977
|
+
// TODO: Other objects that should not be cloned?
|
978
|
+
return objectToBeCloned;
|
979
|
+
}
|
980
|
+
}
|
981
|
+
|
982
|
+
// -- Create new object and clone properties
|
983
|
+
|
984
|
+
// objectClone = new Constructor();
|
985
|
+
// const prototypeClone = Object.create(null);
|
986
|
+
|
987
|
+
const prototypeClone = {};
|
988
|
+
const objectClone = Object.create(prototypeClone);
|
989
|
+
|
990
|
+
// Already push object to _seenObjects
|
991
|
+
// (objectClone will still change as properties are being cloned)
|
992
|
+
_seenObjects.originals.push(objectToBeCloned);
|
993
|
+
_seenObjects.clones.push(objectClone);
|
994
|
+
|
995
|
+
for (const prop in objectToBeCloned) {
|
996
|
+
if (has_own_property.call(objectToBeCloned, prop)) {
|
997
|
+
// Own property -> clone into object
|
998
|
+
objectClone[prop] = clone(objectToBeCloned[prop], _seenObjects);
|
999
|
+
} else {
|
1000
|
+
// Inherited property -> clone into prototype
|
1001
|
+
|
1002
|
+
// @warning
|
1003
|
+
// known issue: all inherited properties are copied into the
|
1004
|
+
// same prototype object!
|
1005
|
+
|
1006
|
+
prototypeClone[prop] = clone(objectToBeCloned[prop], _seenObjects);
|
1007
|
+
}
|
1008
|
+
}
|
1009
|
+
|
1010
|
+
return objectClone;
|
1011
|
+
}
|
1012
|
+
|
1013
|
+
// -----------------------------------------------------------------------------
|
1014
|
+
|
1015
|
+
/**
|
1016
|
+
* Set a read only property in an object
|
1017
|
+
*
|
1018
|
+
* @param {object} obj - Object to set the read only property in
|
1019
|
+
* @param {string} propertyName - Name of the property to set
|
1020
|
+
* @param {any} value - Value to set
|
1021
|
+
*/
|
1022
|
+
export function setReadOnlyProperty(obj, propertyName, value) {
|
1023
|
+
expect.object(obj);
|
1024
|
+
|
1025
|
+
expect.string(propertyName);
|
1026
|
+
|
1027
|
+
// expect.defined(value);
|
1028
|
+
|
1029
|
+
Object.defineProperty(obj, propertyName, {
|
1030
|
+
value,
|
1031
|
+
writable: false,
|
1032
|
+
enumerable: true
|
1033
|
+
});
|
1034
|
+
}
|
1035
|
+
|
1036
|
+
// -----------------------------------------------------------------------------
|
1037
|
+
|
1038
|
+
/**
|
1039
|
+
* Returns a clone of a (nested) object that has a maximum depth
|
1040
|
+
*
|
1041
|
+
* @param {object|array} obj
|
1042
|
+
*
|
1043
|
+
* @returns {object|array} shallow object or array
|
1044
|
+
*/
|
1045
|
+
export function shallowClone(objectOrArray) {
|
1046
|
+
expect.object(objectOrArray);
|
1047
|
+
|
1048
|
+
if (Array.isArray(objectOrArray)) {
|
1049
|
+
// obj is an array
|
1050
|
+
|
1051
|
+
let isShallow = true;
|
1052
|
+
|
1053
|
+
for (let j = 0, n = objectOrArray.length; j < n; j = j + 1) {
|
1054
|
+
const value = objectOrArray[j];
|
1055
|
+
|
1056
|
+
if (value instanceof Object) {
|
1057
|
+
isShallow = false;
|
1058
|
+
break;
|
1059
|
+
}
|
1060
|
+
} // end for
|
1061
|
+
|
1062
|
+
if (isShallow) {
|
1063
|
+
// objectOrArray is already shallow -> nothing to do
|
1064
|
+
return objectOrArray;
|
1065
|
+
}
|
1066
|
+
|
1067
|
+
const outputArray = [];
|
1068
|
+
|
1069
|
+
for (let j = 0, n = objectOrArray.length; j < n; j = j + 1) {
|
1070
|
+
const value = objectOrArray[j];
|
1071
|
+
|
1072
|
+
if (!(value instanceof Object)) {
|
1073
|
+
outputArray.push(value);
|
1074
|
+
}
|
1075
|
+
// else {
|
1076
|
+
// //outputArray.push( null );
|
1077
|
+
// // outputArray.push( $removed );
|
1078
|
+
// }
|
1079
|
+
} // end for
|
1080
|
+
|
1081
|
+
return outputArray;
|
1082
|
+
} else {
|
1083
|
+
// obj is a not an array
|
1084
|
+
|
1085
|
+
let isShallow = true;
|
1086
|
+
|
1087
|
+
for (const key in objectOrArray) {
|
1088
|
+
const value = objectOrArray[key];
|
1089
|
+
|
1090
|
+
if (value instanceof Object) {
|
1091
|
+
isShallow = false;
|
1092
|
+
break;
|
1093
|
+
}
|
1094
|
+
// else {
|
1095
|
+
// // outputObj[ key ] = null;
|
1096
|
+
// // outputObj[ key ] = $removed;
|
1097
|
+
// }
|
1098
|
+
} // end for
|
1099
|
+
|
1100
|
+
if (isShallow) {
|
1101
|
+
// objectOrArray is already shallow -> nothing to do
|
1102
|
+
return objectOrArray;
|
1103
|
+
}
|
1104
|
+
|
1105
|
+
const outputObj = {};
|
1106
|
+
|
1107
|
+
for (const key in objectOrArray) {
|
1108
|
+
const value = objectOrArray[key];
|
1109
|
+
|
1110
|
+
if (!(value instanceof Object)) {
|
1111
|
+
outputObj[key] = value;
|
1112
|
+
}
|
1113
|
+
// else {
|
1114
|
+
// // outputObj[ key ] = null;
|
1115
|
+
// // outputObj[ key ] = $removed;
|
1116
|
+
// }
|
1117
|
+
} // end for
|
1118
|
+
|
1119
|
+
return outputObj;
|
1120
|
+
}
|
1121
|
+
}
|
1122
|
+
|
1123
|
+
// -----------------------------------------------------------------------------
|
1124
|
+
|
1125
|
+
/**
|
1126
|
+
* Update an object
|
1127
|
+
* - Sets the path-value pairs from the updateData in the object
|
1128
|
+
* - Existing values will be overwritten
|
1129
|
+
* - Existing intermediate values (objects, arrays) will be overwritten too
|
1130
|
+
* (if updateData is an object, not an iterable)
|
1131
|
+
*
|
1132
|
+
* @param {object} [obj] - Input object
|
1133
|
+
*
|
1134
|
+
* @param {object|iterable} updateData - Data to update
|
1135
|
+
*
|
1136
|
+
* Note that if using path-value pairs, the order of the pairs is relevant:
|
1137
|
+
* {
|
1138
|
+
* "some": {},
|
1139
|
+
* "some.path": {},
|
1140
|
+
* "some.path.to": 2,
|
1141
|
+
* }
|
1142
|
+
*
|
1143
|
+
* @returns {object} updated object
|
1144
|
+
*/
|
1145
|
+
export function updateObject(obj = null, updateData = null, options) {
|
1146
|
+
// -- Check parameter [obj]
|
1147
|
+
|
1148
|
+
expect.object(obj);
|
1149
|
+
|
1150
|
+
expect.object(updateData);
|
1151
|
+
|
1152
|
+
// -- Update cloned object
|
1153
|
+
|
1154
|
+
let pathValuePairs;
|
1155
|
+
|
1156
|
+
if (!isIterable(updateData)) {
|
1157
|
+
// Convert updateData to path-value pairs (iterable)
|
1158
|
+
|
1159
|
+
const walkArrays = options && options.replaceArrays ? true : false;
|
1160
|
+
|
1161
|
+
pathValuePairs = iterateObjectEntries(updateData, {
|
1162
|
+
expandPathKeys: true,
|
1163
|
+
outputIntermediateNodes: true,
|
1164
|
+
walkArrays
|
1165
|
+
});
|
1166
|
+
|
1167
|
+
// pathValuePairs = Array.from( pathValuePairs );
|
1168
|
+
// console.log( "CHECK", { updateData, pathValuePairs } );
|
1169
|
+
} else {
|
1170
|
+
// Iterable -> assume iterable of path-value pairs
|
1171
|
+
pathValuePairs = updateData;
|
1172
|
+
}
|
1173
|
+
|
1174
|
+
for (const [arrPath, value] of pathValuePairs) {
|
1175
|
+
// console.log( "updateObject:set", arrPath, value );
|
1176
|
+
objectSet(obj, arrPath, value);
|
1177
|
+
}
|
1178
|
+
|
1179
|
+
return obj;
|
1180
|
+
}
|
1181
|
+
|
1182
|
+
// -----------------------------------------------------------------------------
|
1183
|
+
|
1184
|
+
/**
|
1185
|
+
* Copy own properties from an object to another object if they do not
|
1186
|
+
* exist yet.
|
1187
|
+
*
|
1188
|
+
* @param {object} from - Object ot copy properties from
|
1189
|
+
* @param {object} to - Object to copy properties to
|
1190
|
+
*/
|
1191
|
+
export function copyOwnProperties(from, to) {
|
1192
|
+
const propertyNames = Object.getOwnPropertyNames(from);
|
1193
|
+
|
1194
|
+
const nProperties = propertyNames.length;
|
1195
|
+
|
1196
|
+
if (!nProperties) {
|
1197
|
+
return;
|
1198
|
+
}
|
1199
|
+
|
1200
|
+
const firstIsConstructor = 'constructor' === propertyNames[0] ? true : false;
|
1201
|
+
|
1202
|
+
if (1 === nProperties && firstIsConstructor) {
|
1203
|
+
return;
|
1204
|
+
}
|
1205
|
+
|
1206
|
+
const startAt = firstIsConstructor ? 1 : 0;
|
1207
|
+
|
1208
|
+
// console.log("propertyNames", from, propertyNames, startAt);
|
1209
|
+
|
1210
|
+
for (let j = startAt; j < nProperties; j = j + 1) {
|
1211
|
+
const key = propertyNames[j];
|
1212
|
+
|
1213
|
+
const descriptor = Object.getOwnPropertyDescriptor(from, key);
|
1214
|
+
|
1215
|
+
const targetDescriptor = Object.getOwnPropertyDescriptor(to, key);
|
1216
|
+
|
1217
|
+
// Not needed, we copy an instance
|
1218
|
+
// descriptor.value = clone( descriptor.value );
|
1219
|
+
|
1220
|
+
if (!targetDescriptor) {
|
1221
|
+
// Property does not yet exist
|
1222
|
+
Object.defineProperty(to, key, descriptor);
|
1223
|
+
}
|
1224
|
+
} // end for
|
1225
|
+
|
1226
|
+
// >>> FIXME? TODO? copy Symbols too? <<<
|
1227
|
+
}
|
1228
|
+
|
1229
|
+
// -----------------------------------------------------------------------------
|
1230
|
+
|
1231
|
+
/**
|
1232
|
+
* Convert string with dot separated values to a list of values
|
1233
|
+
* - Accepts that the supplied path is already an array path
|
1234
|
+
*
|
1235
|
+
* @param {string|string[]} path
|
1236
|
+
*
|
1237
|
+
* @returns {string[]} list of path values
|
1238
|
+
*/
|
1239
|
+
export function ensureArrayPath(path) {
|
1240
|
+
if (typeof path === 'string') {
|
1241
|
+
return path.split(PATH_SEPARATOR);
|
1242
|
+
} else if (Array.isArray(path)) {
|
1243
|
+
// Nothing to do
|
1244
|
+
return path;
|
1245
|
+
} else {
|
1246
|
+
throw new Error('Missing or invalid parameter [path] (expected string or array)');
|
1247
|
+
}
|
1248
|
+
}
|
1249
|
+
|
1250
|
+
/* ----------------------------------------------------- Internal methods */
|
1251
|
+
|
1252
|
+
// -----------------------------------------------------------------------------
|
1253
|
+
|
1254
|
+
/**
|
1255
|
+
* Create all parent objects on the object path if they do not yet exist yet
|
1256
|
+
* - This method will throw an exception if there is a non-object node in
|
1257
|
+
* the path
|
1258
|
+
*
|
1259
|
+
* @param {object} obj
|
1260
|
+
* Object to create the parent objects in
|
1261
|
+
*
|
1262
|
+
* @param {string[]} arrPath
|
1263
|
+
* The path that specified which parent oobjects to create
|
1264
|
+
*
|
1265
|
+
* @returns {object} the input object with the created object properties
|
1266
|
+
*/
|
1267
|
+
export function _ensureParent(obj, arrPath) {
|
1268
|
+
// console.log("_ensureParent (1)", { obj, arrPath } );
|
1269
|
+
|
1270
|
+
let current = obj;
|
1271
|
+
let prev = current;
|
1272
|
+
|
1273
|
+
for (let j = 0, n_1 = arrPath.length - 1; j < n_1; j = j + 1) {
|
1274
|
+
const key = arrPath[j];
|
1275
|
+
|
1276
|
+
current = current[key];
|
1277
|
+
|
1278
|
+
if (current === undefined || current === null) {
|
1279
|
+
current = prev[key] = {};
|
1280
|
+
prev = current;
|
1281
|
+
continue;
|
1282
|
+
}
|
1283
|
+
|
1284
|
+
const nextKey = arrPath[j + 1];
|
1285
|
+
|
1286
|
+
// Check
|
1287
|
+
// Check if current is an object
|
1288
|
+
// If current is an array, check if the nextKey can be set on a array
|
1289
|
+
|
1290
|
+
if (current instanceof Object) {
|
1291
|
+
if (Array.isArray(current) && j < n_1) {
|
1292
|
+
const nextKeyAsInt = parseInt(nextKey, 10);
|
1293
|
+
|
1294
|
+
if (Number.isNaN(nextKeyAsInt)) {
|
1295
|
+
// console.log("CHECK", { obj, arrPath, j, current, nextKey } );
|
1296
|
+
throw new Error(`Cannot set property [${nextKey}] ` + 'on data node of type [Array]');
|
1297
|
+
}
|
1298
|
+
}
|
1299
|
+
} else {
|
1300
|
+
// console.log( { current, prev, key, arrPath, j } );
|
1301
|
+
|
1302
|
+
throw new Error(
|
1303
|
+
`Cannot set property [${nextKey}] from ` +
|
1304
|
+
`path [${display_array_path(arrPath)}] on data node that is not ` +
|
1305
|
+
'an object or an array'
|
1306
|
+
);
|
1307
|
+
}
|
1308
|
+
|
1309
|
+
prev = current;
|
1310
|
+
} // end for
|
1311
|
+
|
1312
|
+
return current;
|
1313
|
+
}
|
1314
|
+
|
1315
|
+
// -----------------------------------------------------------------------------
|
1316
|
+
|
1317
|
+
/**
|
1318
|
+
* Get parent object at the specified path
|
1319
|
+
*
|
1320
|
+
* @param {object} obj - Object to work in
|
1321
|
+
* @param {string[]} arrPath - Path to get the parent object for
|
1322
|
+
*
|
1323
|
+
* @returns {object|array|null} parent object or null if not found
|
1324
|
+
*/
|
1325
|
+
export function _getParent(obj, arrPath) {
|
1326
|
+
let current = obj;
|
1327
|
+
|
1328
|
+
for (let j = 0, n_1 = arrPath.length - 1; j < n_1; j = j + 1) {
|
1329
|
+
let key = arrPath[j];
|
1330
|
+
|
1331
|
+
current = current[key];
|
1332
|
+
|
1333
|
+
if (typeof current === 'undefined') {
|
1334
|
+
return null;
|
1335
|
+
}
|
1336
|
+
|
1337
|
+
if (current instanceof Object) {
|
1338
|
+
if (Array.isArray(current) && j < n_1 - 1) {
|
1339
|
+
// node is an array
|
1340
|
+
// -> use next part of the array path as (numerical) array index
|
1341
|
+
|
1342
|
+
key = arrPath[j + 1];
|
1343
|
+
const keyAsInt = parseInt(key, 10);
|
1344
|
+
|
1345
|
+
if (Number.isNaN(keyAsInt)) {
|
1346
|
+
throw new Error(
|
1347
|
+
`Cannot get property [${display_array_path(arrPath, j)}]` +
|
1348
|
+
'from data node of type [Array]'
|
1349
|
+
);
|
1350
|
+
}
|
1351
|
+
}
|
1352
|
+
} else {
|
1353
|
+
throw new Error(
|
1354
|
+
`Cannot get property [${display_array_path(arrPath, j)}]` +
|
1355
|
+
'from a data node that is not an object or an array'
|
1356
|
+
);
|
1357
|
+
}
|
1358
|
+
} // end for
|
1359
|
+
|
1360
|
+
return current;
|
1361
|
+
}
|