objwalk 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -290
- package/SPEC.md +383 -0
- package/dist/index.cjs +151 -132
- package/dist/index.d.cts +31 -5
- package/dist/index.d.ts +31 -5
- package/dist/index.js +150 -132
- package/package.json +3 -2
- package/skills/objwalk/SKILL.md +74 -8
package/dist/index.js
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
// src/helpers/createPath.ts
|
|
2
|
+
var createPath = (parentPath, key, parentIsArray) => {
|
|
3
|
+
if (parentIsArray) {
|
|
4
|
+
return parentPath + "[" + key + "]";
|
|
5
|
+
}
|
|
6
|
+
return parentPath ? parentPath + "." + key : String(key);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// src/helpers/isWalkable.ts
|
|
10
|
+
var isWalkable = (value) => {
|
|
11
|
+
return value !== null && typeof value === "object";
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/each.ts
|
|
15
|
+
var each = (data, callback, { root = false, descend } = {}) => {
|
|
16
|
+
const walk = (value, path, segments, key) => {
|
|
17
|
+
const proceed = segments.length > 0 || root ? callback(key, value, path, [...segments]) !== false : true;
|
|
18
|
+
if (proceed && isWalkable(value) && (descend === void 0 || descend({ key, value, path, segments: [...segments] }))) {
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
return value.every(
|
|
21
|
+
(item, index) => walk(item, createPath(path, index, true), [...segments, index], index)
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return Object.keys(value).every(
|
|
25
|
+
(childKey) => walk(value[childKey], createPath(path, childKey, false), [...segments, childKey], childKey)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return proceed;
|
|
29
|
+
};
|
|
30
|
+
walk(data, "", [], void 0);
|
|
31
|
+
};
|
|
32
|
+
|
|
1
33
|
// src/helpers/getValueByKey.ts
|
|
2
34
|
var getValueByKey = (object, key, { inherited = true, ignore = [] } = {}) => {
|
|
3
35
|
if (object === null || object === void 0 || ignore.includes(key) || !inherited && !Object.hasOwn(object, key)) {
|
|
@@ -49,51 +81,6 @@ var parsePathSelection = (path = "") => {
|
|
|
49
81
|
};
|
|
50
82
|
};
|
|
51
83
|
|
|
52
|
-
// src/helpers/createPath.ts
|
|
53
|
-
var createPath = (parentPath, key, parentIsArray) => {
|
|
54
|
-
if (parentIsArray) {
|
|
55
|
-
return parentPath + "[" + key + "]";
|
|
56
|
-
}
|
|
57
|
-
return parentPath ? parentPath + "." + key : String(key);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// src/helpers/isWalkable.ts
|
|
61
|
-
var isWalkable = (value) => {
|
|
62
|
-
return value !== null && typeof value === "object";
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
// src/each.ts
|
|
66
|
-
var each = (data, callback) => {
|
|
67
|
-
const walk = (value, path, key, parentIsArray) => {
|
|
68
|
-
const currentPath = path || createPath("", key, parentIsArray);
|
|
69
|
-
callback(key, value, currentPath);
|
|
70
|
-
if (!isWalkable(value)) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
if (Array.isArray(value)) {
|
|
74
|
-
value.forEach((item, index) => {
|
|
75
|
-
walk(item, createPath(currentPath, index, true), index, true);
|
|
76
|
-
});
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
Object.entries(value).forEach(([childKey, item]) => {
|
|
80
|
-
walk(item, createPath(currentPath, childKey, false), childKey, false);
|
|
81
|
-
});
|
|
82
|
-
};
|
|
83
|
-
if (!isWalkable(data)) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
if (Array.isArray(data)) {
|
|
87
|
-
data.forEach((item, index) => {
|
|
88
|
-
walk(item, createPath("", index, true), index, true);
|
|
89
|
-
});
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
Object.entries(data).forEach(([key, value]) => {
|
|
93
|
-
walk(value, createPath("", key, false), key, false);
|
|
94
|
-
});
|
|
95
|
-
};
|
|
96
|
-
|
|
97
84
|
// src/pick.ts
|
|
98
85
|
var pick = (data = {}, path = "", options = {}) => {
|
|
99
86
|
const { cleanPath, keys } = parsePathSelection(path);
|
|
@@ -187,13 +174,13 @@ var extract = (source, mapping) => {
|
|
|
187
174
|
return;
|
|
188
175
|
}
|
|
189
176
|
if (Array.isArray(sourcePath)) {
|
|
190
|
-
|
|
177
|
+
each(sourcePath, (_key, item) => {
|
|
191
178
|
const value = pick(source, item);
|
|
192
179
|
if (value !== void 0) {
|
|
193
180
|
set(result, destinationPath, value);
|
|
194
|
-
|
|
181
|
+
return false;
|
|
195
182
|
}
|
|
196
|
-
}
|
|
183
|
+
});
|
|
197
184
|
return;
|
|
198
185
|
}
|
|
199
186
|
set(result, destinationPath, pick(source, sourcePath));
|
|
@@ -209,114 +196,144 @@ var map = (data, callback, store = {}) => {
|
|
|
209
196
|
return store;
|
|
210
197
|
};
|
|
211
198
|
|
|
212
|
-
// src/
|
|
213
|
-
var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
199
|
+
// src/helpers/isPlainObject.ts
|
|
214
200
|
var isPlainObject = (value) => {
|
|
215
|
-
if (value
|
|
216
|
-
|
|
201
|
+
if (value !== null && typeof value === "object") {
|
|
202
|
+
const prototype = Object.getPrototypeOf(value);
|
|
203
|
+
return prototype === Object.prototype || prototype === null;
|
|
217
204
|
}
|
|
218
|
-
|
|
219
|
-
return prototype === Object.prototype || prototype === null;
|
|
205
|
+
return false;
|
|
220
206
|
};
|
|
207
|
+
|
|
208
|
+
// src/merge.ts
|
|
209
|
+
var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
221
210
|
var merge = (target, ...sources) => {
|
|
222
211
|
const targetRecord = target;
|
|
223
|
-
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
targetRecord[key] = sourceValue;
|
|
236
|
-
}
|
|
212
|
+
each(sources, (_index, source) => {
|
|
213
|
+
if (isPlainObject(source)) {
|
|
214
|
+
each(Object.entries(source), (_index2, [key, sourceValue]) => {
|
|
215
|
+
if (unsafeKeys.has(key) === false) {
|
|
216
|
+
const targetValue = targetRecord[key];
|
|
217
|
+
if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
|
|
218
|
+
merge(targetValue, sourceValue);
|
|
219
|
+
} else {
|
|
220
|
+
targetRecord[key] = sourceValue;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}, { descend: ({ segments }) => segments.length === 0 });
|
|
237
224
|
}
|
|
238
|
-
}
|
|
225
|
+
}, { descend: ({ segments }) => segments.length === 0 });
|
|
239
226
|
return target;
|
|
240
227
|
};
|
|
241
228
|
|
|
242
229
|
// src/flush.ts
|
|
243
230
|
var flush = (data = {}, path) => {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
231
|
+
const paths = Array.isArray(path) ? path : [path];
|
|
232
|
+
each(paths, (_key, path2) => {
|
|
233
|
+
if (path2.length > 0) {
|
|
234
|
+
const keys = path2.split(".");
|
|
235
|
+
const lastKey = keys.pop() ?? "";
|
|
236
|
+
const match = lastKey.match(/^(\w+)\[(\d+)\]$/);
|
|
237
|
+
let parent = getValueByPath(data, keys.join("."), { inherited: false });
|
|
238
|
+
let key = lastKey;
|
|
239
|
+
if (match) {
|
|
240
|
+
const [, arrayKey, index] = match;
|
|
241
|
+
const array = getValueByKey(parent, arrayKey, { inherited: false });
|
|
242
|
+
parent = Array.isArray(array) ? array : void 0;
|
|
243
|
+
key = index;
|
|
244
|
+
}
|
|
245
|
+
if (parent !== null && typeof parent === "object" && Object.hasOwn(parent, key)) {
|
|
246
|
+
delete parent[key];
|
|
247
|
+
}
|
|
258
248
|
}
|
|
259
|
-
}
|
|
249
|
+
});
|
|
260
250
|
return data;
|
|
261
251
|
};
|
|
262
252
|
|
|
263
253
|
// src/deepEqual.ts
|
|
264
254
|
var deepEqual = (x, y, store) => {
|
|
265
|
-
if (Number.isNaN(x) && Number.isNaN(y)) {
|
|
255
|
+
if (x === y || Number.isNaN(x) && Number.isNaN(y)) {
|
|
266
256
|
return true;
|
|
267
257
|
}
|
|
268
|
-
if (x === y) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
258
|
+
if (x !== null && y !== null && typeof x === "object" && typeof y === "object") {
|
|
259
|
+
let seen = store.get(x);
|
|
260
|
+
if (seen !== void 0 && seen.has(y)) {
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
if (seen === void 0) {
|
|
264
|
+
seen = /* @__PURE__ */ new WeakSet();
|
|
265
|
+
store.set(x, seen);
|
|
266
|
+
}
|
|
267
|
+
seen.add(y);
|
|
268
|
+
if (x instanceof Date || y instanceof Date) {
|
|
269
|
+
return x instanceof Date && y instanceof Date && x.getTime() === y.getTime();
|
|
270
|
+
}
|
|
271
|
+
if (x instanceof RegExp || y instanceof RegExp) {
|
|
272
|
+
return x instanceof RegExp && y instanceof RegExp && x.toString() === y.toString();
|
|
273
|
+
}
|
|
274
|
+
if (Array.isArray(x) && Array.isArray(y)) {
|
|
275
|
+
const { length } = x;
|
|
276
|
+
let equal = length === y.length;
|
|
277
|
+
let index = 0;
|
|
278
|
+
while (equal && index < length) {
|
|
279
|
+
equal = deepEqual(x[index], y[index], store);
|
|
280
|
+
index += 1;
|
|
281
|
+
}
|
|
282
|
+
return equal;
|
|
283
|
+
} else if (Array.isArray(x) === false && Array.isArray(y) === false) {
|
|
284
|
+
const keysX = Reflect.ownKeys(x);
|
|
285
|
+
const keysY = Reflect.ownKeys(y);
|
|
286
|
+
return keysX.length === keysY.length && keysX.every(
|
|
287
|
+
(key) => Object.hasOwn(y, key) && deepEqual(x[key], y[key], store)
|
|
288
|
+
);
|
|
289
|
+
}
|
|
299
290
|
}
|
|
300
|
-
return
|
|
291
|
+
return false;
|
|
301
292
|
};
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
return false;
|
|
293
|
+
|
|
294
|
+
// src/equally.ts
|
|
295
|
+
var equally = (a, b, paths) => {
|
|
296
|
+
if (paths !== void 0) {
|
|
297
|
+
return paths.every((path) => equally(getValueByPath(a, path), getValueByPath(b, path)));
|
|
308
298
|
}
|
|
309
|
-
return
|
|
310
|
-
|
|
311
|
-
|
|
299
|
+
return deepEqual(a, b, /* @__PURE__ */ new WeakMap());
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// src/diff.ts
|
|
303
|
+
var diff = (first, last) => {
|
|
304
|
+
const operations = [];
|
|
305
|
+
const frames = [];
|
|
306
|
+
const finish = (depth) => {
|
|
307
|
+
each(frames.splice(depth).reverse(), (_index, { first: first2, last: last2, path }) => {
|
|
308
|
+
each(Object.keys(first2), (_index2, key) => {
|
|
309
|
+
if (Object.prototype.propertyIsEnumerable.call(last2, key) === false) {
|
|
310
|
+
operations.push({ type: "delete", path: [...path, key] });
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}, { descend: ({ segments }) => segments.length === 0 });
|
|
314
|
+
return frames.at(-1);
|
|
315
|
+
};
|
|
316
|
+
let descend = false;
|
|
317
|
+
each(last, (key, value, _path, segments) => {
|
|
318
|
+
const path = segments.map(String);
|
|
319
|
+
const frame = finish(segments.length);
|
|
320
|
+
const present = key === void 0 || frame !== void 0 && Object.prototype.propertyIsEnumerable.call(frame.first, key);
|
|
321
|
+
const previous = key === void 0 ? first : present ? frame?.first[key] : void 0;
|
|
322
|
+
descend = false;
|
|
323
|
+
if (present && isPlainObject(previous) && isPlainObject(value)) {
|
|
324
|
+
frames.push({ first: previous, last: value, path });
|
|
325
|
+
descend = true;
|
|
326
|
+
} else if (present === false || equally(previous, value) === false) {
|
|
327
|
+
operations.push({ type: "set", path, value });
|
|
312
328
|
}
|
|
313
|
-
|
|
329
|
+
}, {
|
|
330
|
+
root: true,
|
|
331
|
+
descend: () => descend
|
|
314
332
|
});
|
|
333
|
+
finish(0);
|
|
334
|
+
return operations;
|
|
315
335
|
};
|
|
316
336
|
|
|
317
|
-
// src/equally.ts
|
|
318
|
-
var equally = (a, b) => deepEqual(a, b, /* @__PURE__ */ new WeakMap());
|
|
319
|
-
|
|
320
337
|
// src/clone.ts
|
|
321
338
|
var clone = (value) => {
|
|
322
339
|
const store = /* @__PURE__ */ new WeakMap();
|
|
@@ -344,9 +361,9 @@ var clone = (value) => {
|
|
|
344
361
|
}
|
|
345
362
|
const result = {};
|
|
346
363
|
store.set(obj, result);
|
|
347
|
-
|
|
364
|
+
each(Reflect.ownKeys(obj), (_index, key) => {
|
|
348
365
|
result[key] = cloneValue(obj[key]);
|
|
349
|
-
}
|
|
366
|
+
});
|
|
350
367
|
return result;
|
|
351
368
|
};
|
|
352
369
|
return cloneValue(value);
|
|
@@ -386,6 +403,7 @@ var stringify = (value, { fallback, onError, replacer, space } = {}) => {
|
|
|
386
403
|
};
|
|
387
404
|
export {
|
|
388
405
|
clone,
|
|
406
|
+
diff,
|
|
389
407
|
each,
|
|
390
408
|
equally,
|
|
391
409
|
extract,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "objwalk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Small utilities for reading, writing, walking, and reshaping nested JavaScript objects.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"object",
|
|
@@ -40,11 +40,12 @@
|
|
|
40
40
|
"files": [
|
|
41
41
|
"dist",
|
|
42
42
|
"README.md",
|
|
43
|
+
"SPEC.md",
|
|
43
44
|
"skills"
|
|
44
45
|
],
|
|
45
46
|
"scripts": {
|
|
46
47
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
47
|
-
"test": "vitest run",
|
|
48
|
+
"test": "tsc --project tsconfig.test.json && vitest run",
|
|
48
49
|
"pack:check": "npm pack --dry-run"
|
|
49
50
|
},
|
|
50
51
|
"prepublishOnly": "npm run build",
|
package/skills/objwalk/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: objwalk
|
|
3
|
-
description: Use when writing or editing JavaScript or TypeScript that imports `objwalk`, reads, writes, or deletes nested object paths, reshapes or walks objects, or uses objwalk JSON helpers.
|
|
3
|
+
description: Use when writing or editing JavaScript or TypeScript that imports `objwalk`, reads, writes, or deletes nested object paths, computes differences, reshapes or walks objects, or uses objwalk JSON helpers.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# objwalk
|
|
@@ -19,7 +19,7 @@ them with type assertions or assume every method accepts arbitrary `unknown`.
|
|
|
19
19
|
Named imports only:
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
|
-
import { clone, each, equally, extract, flush, map, merge, parse, pick, set, stringify } from 'objwalk'
|
|
22
|
+
import { clone, diff, each, equally, extract, flush, map, merge, parse, pick, set, stringify } from 'objwalk'
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Choose the existing operation
|
|
@@ -28,16 +28,17 @@ import { clone, each, equally, extract, flush, map, merge, parse, pick, set, str
|
|
|
28
28
|
| --- | --- |
|
|
29
29
|
| Read a nested value | `pick(data, path, options?)`; unresolved paths return `undefined`. |
|
|
30
30
|
| Write a nested value | `set(data, path, value)`; creates intermediate containers, mutates and returns `data`. |
|
|
31
|
-
| Delete
|
|
31
|
+
| Delete nested properties | `flush(data, path)`; accepts one path or `string[]`, deletes own properties, mutates and returns `data`; missing paths are left unchanged. |
|
|
32
32
|
| Reshape an object | `extract(source, mapping)`; mapping keys are destination paths, values are source paths. |
|
|
33
|
-
| Visit nested values | `each(data, (key, value, path) => { ... })`;
|
|
33
|
+
| Visit nested values | `each(data, (key, value, path, segments) => { ... }, options?)`; supports objects and arrays, root is excluded by default; returning `false` stops the entire traversal. |
|
|
34
34
|
| Build a store while walking | `map(data, (key, value, store, path) => { ... }, store?)`; returns the store, ignores callback return values. |
|
|
35
35
|
| Deep merge | `merge(target, ...sources)`; mutates and returns the target, merges plain objects, replaces arrays and non-plain values. |
|
|
36
36
|
| Deep copy | `clone(value)`; preserves `Date`, `RegExp`, symbol keys, and circular references. |
|
|
37
|
-
| Deep comparison | `equally(a, b)`; supports `NaN`, `Date`, `RegExp`, and circular references. |
|
|
37
|
+
| Deep comparison | `equally(a, b, paths?)`; supports `NaN`, `Date`, `RegExp`, and circular references; optional `string[]` compares only selected paths. |
|
|
38
|
+
| Describe changes | `diff(first, last): DiffOperation[]`; returns `set`/`delete` operations, or `[]` when unchanged. |
|
|
38
39
|
|
|
39
40
|
`set`, `flush`, and `merge` mutate their targets. Use `clone` first only when the task
|
|
40
|
-
requires an independent copy. `each` and `
|
|
41
|
+
requires an independent copy. `each`, `map`, and `diff` do not support circular traversal;
|
|
41
42
|
do not infer that capability from `clone` or `equally`.
|
|
42
43
|
|
|
43
44
|
## Paths and reshaping
|
|
@@ -52,7 +53,8 @@ do not infer that capability from `clone` or `equally`.
|
|
|
52
53
|
|
|
53
54
|
`[n]` is an `extract` mapping feature, not a general wildcard for `pick`, `set`, or `flush`.
|
|
54
55
|
An array of source paths in `extract` selects the first result that is not
|
|
55
|
-
`undefined`, preserving `null`, `false`, `0`, and empty strings
|
|
56
|
+
`undefined`, preserving `null`, `false`, `0`, and empty strings; later paths are
|
|
57
|
+
not read. Sparse path arrays in `flush` and fallback arrays in `extract` skip holes.
|
|
56
58
|
|
|
57
59
|
Use `flush` to remove a property rather than `set(data, path, undefined)`, which
|
|
58
60
|
leaves the property present. `flush` traverses and deletes only own properties.
|
|
@@ -62,6 +64,18 @@ but deleting an item leaves a hole without shifting indexes or changing length.
|
|
|
62
64
|
Selection syntax (`{name,email}`, `{*}`) is not supported. Errors from getters,
|
|
63
65
|
proxies, or deleting non-configurable properties propagate.
|
|
64
66
|
|
|
67
|
+
`flush` processes a path array in order; `[]` leaves the target unchanged.
|
|
68
|
+
`equally` without `paths` (or with `undefined`) compares whole values. With `paths`,
|
|
69
|
+
every selected value must match; `[]` returns true and `''` selects the whole input.
|
|
70
|
+
It uses dot notation and explicit array indexes, reading inherited properties like
|
|
71
|
+
`pick`; unresolved paths compare as `undefined`, including against an explicit
|
|
72
|
+
`undefined` value. Selection syntax and `[n]` mappings are not supported.
|
|
73
|
+
Getter/proxy errors propagate.
|
|
74
|
+
|
|
75
|
+
`equally` compares `Date` and `RegExp` only with their respective types. Arrays
|
|
76
|
+
compare every index, including holes, which read as `undefined`; holes match
|
|
77
|
+
explicit `undefined` slots but not other values.
|
|
78
|
+
|
|
65
79
|
`pick` reads inherited properties by default. Use its existing options when own
|
|
66
80
|
properties or ignored keys are required:
|
|
67
81
|
|
|
@@ -72,6 +86,57 @@ pick(data, 'user.name', {
|
|
|
72
86
|
})
|
|
73
87
|
```
|
|
74
88
|
|
|
89
|
+
## Walking
|
|
90
|
+
|
|
91
|
+
`each` passes literal `segments: WalkKey[]` as its fourth callback argument.
|
|
92
|
+
Object keys are strings; array indexes are numbers. Prefer segments when keys may
|
|
93
|
+
contain dots, brackets, or empty strings; the string `path` can be ambiguous.
|
|
94
|
+
|
|
95
|
+
`EachOptions` accepts `root` (default `false`) and
|
|
96
|
+
`descend: ({ key, value, path, segments }) => boolean`.
|
|
97
|
+
With `root: true`, the root is visited first, including primitive/nullish values,
|
|
98
|
+
with key `undefined`, path `''`, and segments `[]`. Callback keys are then typed
|
|
99
|
+
`WalkKey | undefined`; legacy calls keep `WalkKey`. Use the public
|
|
100
|
+
`EachCallback<WalkKey | undefined>` for an explicitly typed root-aware callback.
|
|
101
|
+
`EachEntry` and `EachOptions` are also public named exports.
|
|
102
|
+
|
|
103
|
+
`descend` is called only for containers, after their callback, and returning false
|
|
104
|
+
skips their children. It also runs for a root container excluded from callbacks.
|
|
105
|
+
For example, `descend: ({ value }) => Array.isArray(value) === false` visits arrays
|
|
106
|
+
without traversing their elements. Returning exactly `false` from the callback stops
|
|
107
|
+
the entire traversal, including siblings and ancestor branches; other return values
|
|
108
|
+
are ignored. The stopped container's `descend` is not called, and its children and
|
|
109
|
+
subsequent values are not read. Object keys are collected on entering a container;
|
|
110
|
+
values are read as visited. `each` still returns `undefined`. Existing callbacks
|
|
111
|
+
that returned `false` must omit that return to keep walking. `map` continues to
|
|
112
|
+
ignore callback return values for both objects and arrays.
|
|
113
|
+
Mutating received segments does not affect traversal. Callback/getter/descend errors
|
|
114
|
+
propagate. Cycles are not detected.
|
|
115
|
+
|
|
116
|
+
## Differences
|
|
117
|
+
|
|
118
|
+
`diff` recursively compares two plain objects by their own enumerable string keys;
|
|
119
|
+
symbol/non-enumerable keys, prototypes, and descriptors are not reported during that traversal.
|
|
120
|
+
Arrays and other values use `equally` and are replaced as a whole when changed.
|
|
121
|
+
New properties are also set as whole values. The public `DiffOperation` type is:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
type DiffOperation =
|
|
125
|
+
| { type: 'set'; path: string[]; value: unknown }
|
|
126
|
+
| { type: 'delete'; path: string[] }
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Paths contain literal keys; `[]` replaces the root. Do not join them with `.` to
|
|
130
|
+
call `set` or `flush`: keys may contain dots or brackets. `merge` does not apply
|
|
131
|
+
these operations. There is no patch applier or options argument.
|
|
132
|
+
|
|
133
|
+
Deletion is distinct from assigning `null` or `undefined`, including adding an own
|
|
134
|
+
`undefined` property. Empty parent objects remain. Inputs are not mutated or cloned;
|
|
135
|
+
`set.value` retains references from `last`. Native JSON serialization loses an
|
|
136
|
+
`undefined` value, so do not assume lossless transport for arbitrary JS values.
|
|
137
|
+
Getter/proxy errors propagate. Cycles are outside the contract, without detection;
|
|
138
|
+
shared references without cycles work.
|
|
139
|
+
|
|
75
140
|
## JSON and ready values
|
|
76
141
|
|
|
77
142
|
Call `parse(raw, options?)` directly with `raw: unknown`. The library chooses
|
|
@@ -101,5 +166,6 @@ Fallback applies only to exceptions. Preserve valid falsy results and native
|
|
|
101
166
|
`undefined`. Neither fallback nor a throwing `onError` changes stringify's
|
|
102
167
|
return type of `string | undefined`.
|
|
103
168
|
|
|
104
|
-
Full usage: [README](../../README.md).
|
|
169
|
+
Full usage: [README](../../README.md). Edge cases: [specification](../../SPEC.md).
|
|
170
|
+
In a consumer project, read the installed
|
|
105
171
|
package's README and declarations when checking the available version's API.
|