objwalk 1.6.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 +125 -373
- package/SPEC.md +383 -0
- package/dist/index.cjs +89 -98
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +89 -98
- package/package.json +3 -2
- package/skills/objwalk/SKILL.md +28 -8
package/README.md
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
# objwalk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Work with nested JavaScript data without writing the same traversal code again.
|
|
4
|
+
Read and update values by path, reshape API responses, walk objects and arrays,
|
|
5
|
+
and see exactly what changed between two states.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
- **One path syntax** for nested properties and array indexes.
|
|
8
|
+
- **Tools that work together:** pick, set, reshape, walk, copy, merge, and compare.
|
|
9
|
+
- **No runtime dependencies**, with TypeScript declarations and ESM/CommonJS builds.
|
|
6
10
|
|
|
7
|
-
> Coding agent? Load the [objwalk skill](skills/objwalk/SKILL.md).
|
|
11
|
+
> 🤖 Coding agent? Load the [objwalk skill](https://github.com/khalilov/objwalk/blob/main/skills/objwalk/SKILL.md).
|
|
12
|
+
> Need every edge case? Open the [full specification](https://github.com/khalilov/objwalk/blob/main/SPEC.md).
|
|
8
13
|
|
|
9
14
|
## Install
|
|
10
15
|
|
|
@@ -14,440 +19,187 @@ npm install objwalk
|
|
|
14
19
|
|
|
15
20
|
## Usage
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
import { diff, each, equally, extract, flush, map, merge, parse, pick, set, stringify } from 'objwalk'
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## pick
|
|
22
|
-
|
|
23
|
-
Read a value from an object by path.
|
|
22
|
+
Import the tools you need and use paths to reach nested values:
|
|
24
23
|
|
|
25
24
|
```ts
|
|
26
|
-
|
|
27
|
-
user: {
|
|
28
|
-
name: 'Natalia Romanoff',
|
|
29
|
-
email: 'black.widow@example.com',
|
|
30
|
-
},
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
pick(data, 'user.name')
|
|
34
|
-
// 'Natalia Romanoff'
|
|
35
|
-
```
|
|
25
|
+
import { pick, set, flush } from 'objwalk'
|
|
36
26
|
|
|
37
|
-
Pick several keys from the same nested object.
|
|
38
|
-
|
|
39
|
-
```ts
|
|
40
|
-
pick(data, 'user.{name,email}')
|
|
41
|
-
// { name: 'Natalia Romanoff', email: 'black.widow@example.com' }
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
List keys from a nested object.
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
pick(data, 'user.{*}')
|
|
48
|
-
// ['name', 'email']
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Read from arrays with an explicit index.
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
27
|
const data = {
|
|
55
|
-
|
|
28
|
+
user: { name: 'Natalia Romanoff' },
|
|
29
|
+
items: [{ title: 'Keyboard', internalId: 'draft-1' }],
|
|
56
30
|
}
|
|
57
31
|
|
|
58
|
-
pick(data, 'items[
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
```ts
|
|
68
|
-
pick(data, path, {
|
|
69
|
-
inherited: false,
|
|
70
|
-
ignore: ['__proto__', 'prototype', 'constructor'],
|
|
71
|
-
})
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
The options apply to every path segment, array index, and selected key. With `{*}`, ignored
|
|
75
|
-
keys are omitted from the returned list.
|
|
76
|
-
|
|
77
|
-
## set
|
|
78
|
-
|
|
79
|
-
Set a value inside an object by path.
|
|
80
|
-
|
|
81
|
-
```ts
|
|
82
|
-
const data = {}
|
|
83
|
-
|
|
84
|
-
set(data, 'user.name', 'Natalia')
|
|
85
|
-
// { user: { name: 'Natalia' } }
|
|
32
|
+
pick(data, 'items[0].title') // 'Keyboard'
|
|
33
|
+
set(data, 'user.email', 'black.widow@example.com')
|
|
34
|
+
flush(data, ['user.name', 'items[0].internalId'])
|
|
35
|
+
// data is now:
|
|
36
|
+
// {
|
|
37
|
+
// user: { email: 'black.widow@example.com' },
|
|
38
|
+
// items: [{ title: 'Keyboard' }],
|
|
39
|
+
// }
|
|
86
40
|
```
|
|
87
41
|
|
|
88
|
-
|
|
42
|
+
`set`, `flush`, and `merge` update their target and return it. Use `clone` first
|
|
43
|
+
when you need an independent copy of supported data.
|
|
89
44
|
|
|
90
|
-
|
|
91
|
-
const data = {}
|
|
45
|
+
## Paths at a glance
|
|
92
46
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
47
|
+
| Path | What it does |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| `user.name` | Access a nested property |
|
|
50
|
+
| `items[0].title` | Access an array item's property |
|
|
51
|
+
| `user.{name,email}` | Select several keys with `pick` |
|
|
52
|
+
| `user.{*}` | List keys with `pick` |
|
|
53
|
+
| `items[n].title` | Map array items with `extract` |
|
|
96
54
|
|
|
97
|
-
|
|
55
|
+
A missing path returns `undefined` with `pick`. Selection and array mapping have
|
|
56
|
+
specific uses; see the [path rules](SPEC.md#paths) for the full syntax.
|
|
98
57
|
|
|
99
|
-
##
|
|
58
|
+
## Reshape an API response
|
|
100
59
|
|
|
101
|
-
|
|
60
|
+
Describe the shape you want instead of assembling it field by field.
|
|
61
|
+
Use a list of source paths for fallbacks and `[n]` to map array items:
|
|
102
62
|
|
|
103
63
|
```ts
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
flush(data, 'user.name')
|
|
107
|
-
// { user: { role: 'admin' } }
|
|
64
|
+
import { extract } from 'objwalk'
|
|
108
65
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
`flush(data, path)` mutates and returns the same object. Missing paths, `null`,
|
|
114
|
-
`undefined`, and primitive parents are left unchanged. It does not create intermediate
|
|
115
|
-
containers or remove empty parents. An empty path leaves the object unchanged.
|
|
116
|
-
Only own properties are traversed and deleted; inherited properties are left unchanged.
|
|
117
|
-
|
|
118
|
-
Array indexes use the same explicit syntax as `pick` and `set`:
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
const data = { items: [{ title: 'First' }, { title: 'Second' }] }
|
|
66
|
+
const response = {
|
|
67
|
+
profile: { fullName: 'Natalia Romanoff' },
|
|
68
|
+
products: [{ name: 'Keyboard' }, { name: 'Mouse' }],
|
|
69
|
+
}
|
|
122
70
|
|
|
123
|
-
|
|
124
|
-
|
|
71
|
+
const result = extract(response, {
|
|
72
|
+
'user.name': ['profile.displayName', 'profile.fullName'],
|
|
73
|
+
'items[n].title': 'products[n].name',
|
|
74
|
+
})
|
|
75
|
+
// {
|
|
76
|
+
// user: { name: 'Natalia Romanoff' },
|
|
77
|
+
// items: [{ title: 'Keyboard' }, { title: 'Mouse' }],
|
|
78
|
+
// }
|
|
125
79
|
```
|
|
126
80
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
Errors from getters, proxies, or deleting non-configurable properties propagate to the caller.
|
|
81
|
+
`extract` builds a new result object. Selected object values keep their references;
|
|
82
|
+
use `clone` when you also need to copy those values.
|
|
130
83
|
|
|
131
|
-
##
|
|
84
|
+
## Compare values and track changes
|
|
132
85
|
|
|
133
|
-
|
|
86
|
+
Compare a whole state, check only the fields that matter, or get a list of changes:
|
|
134
87
|
|
|
135
88
|
```ts
|
|
136
|
-
|
|
137
|
-
// true
|
|
138
|
-
|
|
139
|
-
equally({ a: 1 }, { a: 1, b: 2 })
|
|
140
|
-
// false
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
`equally` treats `NaN` as equal to `NaN`, compares `Date` by timestamp and `RegExp` by
|
|
144
|
-
source and flags, and follows circular references without looping.
|
|
89
|
+
import { equally, diff } from 'objwalk'
|
|
145
90
|
|
|
146
|
-
|
|
91
|
+
const before = { user: { name: 'Natalia' }, hp: 10, target: 'enemy' }
|
|
92
|
+
const after = { user: { name: 'Natalia' }, hp: 8 }
|
|
147
93
|
|
|
148
|
-
|
|
94
|
+
equally(before, after) // false
|
|
95
|
+
equally(before, after, ['user.name']) // true
|
|
149
96
|
|
|
150
|
-
|
|
151
|
-
import { diff, type DiffOperation } from 'objwalk'
|
|
152
|
-
|
|
153
|
-
const operations: DiffOperation[] = diff(
|
|
154
|
-
{ hp: 10, target: 'enemy', position: { x: 1, y: 2 } },
|
|
155
|
-
{ hp: null, position: { x: 3, y: 2 } }
|
|
156
|
-
)
|
|
97
|
+
diff(before, after)
|
|
157
98
|
// [
|
|
158
|
-
// { type: 'set', path: ['hp'], value:
|
|
159
|
-
// { type: 'set', path: ['position', 'x'], value: 3 },
|
|
99
|
+
// { type: 'set', path: ['hp'], value: 8 },
|
|
160
100
|
// { type: 'delete', path: ['target'] },
|
|
161
101
|
// ]
|
|
162
|
-
|
|
163
|
-
diff({}, { a: undefined })
|
|
164
|
-
// [{ type: 'set', path: ['a'], value: undefined }]
|
|
165
|
-
|
|
166
|
-
diff({ a: 1 }, {})
|
|
167
|
-
// [{ type: 'delete', path: ['a'] }]
|
|
168
|
-
|
|
169
|
-
diff(1, null)
|
|
170
|
-
// [{ type: 'set', path: [], value: null }]
|
|
171
|
-
|
|
172
|
-
diff({ a: 1 }, { a: 1 }) // []
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
- When both values are plain objects (with `Object.prototype` or a null prototype),
|
|
176
|
-
`diff` recursively compares own enumerable string keys. Symbol and non-enumerable
|
|
177
|
-
keys are ignored during that traversal, and prototype or descriptor changes are not reported.
|
|
178
|
-
- Arrays and other values use `equally`; changed values are replaced as a whole.
|
|
179
|
-
Newly added properties also use a single `set`, including when their value is an empty object.
|
|
180
|
-
- A `set` carries the new value, including `null` or `undefined`; a `delete` means
|
|
181
|
-
the key is absent from the final object's enumerable keys. Empty parents remain.
|
|
182
|
-
- Paths are arrays of literal string keys, so dots, brackets, and empty keys are unambiguous.
|
|
183
|
-
An empty path `[]` means replacement of the root. These are not the string paths
|
|
184
|
-
accepted by `pick`, `set`, or `flush`; joining segments with `.` can lose information.
|
|
185
|
-
- Operations follow the final object's key order, recursively, then report removed
|
|
186
|
-
keys in the first object's key order at each level. No changes return `[]`.
|
|
187
|
-
|
|
188
|
-
`diff(first, last): DiffOperation[]` does not mutate or clone either input. Values in `set`
|
|
189
|
-
operations retain references from `last`; this is not an independent snapshot.
|
|
190
|
-
Getter and proxy errors propagate. Inputs must be acyclic; cycle detection is not performed.
|
|
191
|
-
Shared references without cycles are supported. There is no options argument or patch applier.
|
|
192
|
-
Native JSON serialization drops an `undefined` operation value, so these operations are not
|
|
193
|
-
a lossless JSON transport format for arbitrary JavaScript values.
|
|
194
|
-
|
|
195
|
-
## merge
|
|
196
|
-
|
|
197
|
-
Deeply merge plain objects into a target object.
|
|
198
|
-
|
|
199
|
-
```ts
|
|
200
|
-
const target = { user: { name: 'Ada', flags: { admin: false } } }
|
|
201
|
-
|
|
202
|
-
merge(target, { user: { email: 'ada@example.com', flags: { admin: true } } })
|
|
203
|
-
// {
|
|
204
|
-
// user: { name: 'Ada', email: 'ada@example.com', flags: { admin: true } },
|
|
205
|
-
// }
|
|
206
102
|
```
|
|
207
103
|
|
|
208
|
-
`
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
Create a new object by mapping destination paths to source paths.
|
|
104
|
+
`equally` supports `NaN`, `Date`, `RegExp`, and circular references. `diff` returns
|
|
105
|
+
changes without applying them; its paths are arrays of literal keys, and changed
|
|
106
|
+
arrays are replaced as a whole. Its inputs must be acyclic.
|
|
213
107
|
|
|
214
|
-
|
|
215
|
-
const source = {
|
|
216
|
-
profile: {
|
|
217
|
-
fullName: 'Natalia Romanoff',
|
|
218
|
-
email: 'black.widow@example.com',
|
|
219
|
-
},
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
extract(source, {
|
|
223
|
-
'user.name': 'profile.fullName',
|
|
224
|
-
'user.email': 'profile.email',
|
|
225
|
-
})
|
|
226
|
-
// {
|
|
227
|
-
// user: {
|
|
228
|
-
// name: 'Natalia Romanoff',
|
|
229
|
-
// email: 'black.widow@example.com',
|
|
230
|
-
// },
|
|
231
|
-
// }
|
|
232
|
-
```
|
|
108
|
+
## Walk objects and arrays
|
|
233
109
|
|
|
234
|
-
|
|
110
|
+
`each` visits nested values and tells you where they live:
|
|
235
111
|
|
|
236
112
|
```ts
|
|
237
|
-
|
|
238
|
-
'user.name': ['profile.displayName', 'profile.fullName'],
|
|
239
|
-
})
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
Use `[n]` to map over arrays.
|
|
243
|
-
|
|
244
|
-
```ts
|
|
245
|
-
const source = {
|
|
246
|
-
products: [
|
|
247
|
-
{ name: 'Keyboard' },
|
|
248
|
-
{ name: 'Mouse' },
|
|
249
|
-
],
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
extract(source, {
|
|
253
|
-
'items[n].title': 'products[n].name',
|
|
254
|
-
})
|
|
255
|
-
// {
|
|
256
|
-
// items: [
|
|
257
|
-
// { title: 'Keyboard' },
|
|
258
|
-
// { title: 'Mouse' },
|
|
259
|
-
// ],
|
|
260
|
-
// }
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
## each
|
|
113
|
+
import { each } from 'objwalk'
|
|
264
114
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
console.log(key, value, path, segments)
|
|
115
|
+
each({ user: { name: 'Natalia' }, tags: ['ts'] }, (_key, value, path) => {
|
|
116
|
+
if (typeof value === 'string') {
|
|
117
|
+
console.log(path, value)
|
|
118
|
+
}
|
|
270
119
|
})
|
|
120
|
+
// user.name Natalia
|
|
121
|
+
// tags[0] ts
|
|
271
122
|
```
|
|
272
123
|
|
|
273
|
-
|
|
124
|
+
Return `false` to stop the entire walk. For more control, `each` also accepts
|
|
125
|
+
`root` and `descend` options and provides literal path segments as a fourth
|
|
126
|
+
callback argument. [Traversal details](SPEC.md#each).
|
|
274
127
|
|
|
275
|
-
|
|
276
|
-
- `value`: the current value
|
|
277
|
-
- `path`: the full string path to the current value
|
|
278
|
-
- `segments`: literal path segments (`WalkKey[]`), with string object keys and numeric array indexes
|
|
279
|
-
|
|
280
|
-
For example, a literal key `'user.name'` has segments `['user.name']`, while a nested
|
|
281
|
-
`user.name` has `['user', 'name']`. `items[0].id` has `['items', 0, 'id']`.
|
|
282
|
-
The first three callback arguments keep their existing meaning; old callbacks still work.
|
|
283
|
-
Callback return values are ignored, including `false`.
|
|
284
|
-
|
|
285
|
-
Pass options as the third argument:
|
|
128
|
+
Use `map` when the walk should build a result:
|
|
286
129
|
|
|
287
130
|
```ts
|
|
288
|
-
|
|
289
|
-
// The root is visited first with key undefined, path '', and segments [].
|
|
290
|
-
}, {
|
|
291
|
-
root: true,
|
|
292
|
-
// Visit arrays themselves, but skip their contents.
|
|
293
|
-
descend: ({ value }) => Array.isArray(value) === false,
|
|
294
|
-
})
|
|
295
|
-
```
|
|
131
|
+
import { map, set } from 'objwalk'
|
|
296
132
|
|
|
297
|
-
|
|
298
|
-
| --- | --- | --- |
|
|
299
|
-
| `root` | Visit the root, including primitive, nullish, or empty root values | `false` |
|
|
300
|
-
| `descend` | Receive `{ key, value, path, segments }` and decide whether to visit a container's children | Visit all children |
|
|
301
|
-
|
|
302
|
-
`descend` runs only for objects and arrays, after their callback. It also runs for the
|
|
303
|
-
root container when `root` is false, allowing the entire traversal to be skipped.
|
|
304
|
-
Returning `false` skips children, without evaluating their getters; the container itself
|
|
305
|
-
is still visited when applicable. Errors from callbacks, getters, and `descend` propagate.
|
|
306
|
-
Mutating the received `segments` arrays does not change traversal paths.
|
|
307
|
-
|
|
308
|
-
`EachCallback`, `EachEntry`, and `EachOptions` are public named type exports.
|
|
309
|
-
Without `root`, or with `root: false`, callback keys remain `WalkKey`. With `root: true`
|
|
310
|
-
or a dynamic `root` option, keys are `WalkKey | undefined`. An explicitly typed callback
|
|
311
|
-
can use `EachCallback<WalkKey | undefined>` when root visits are possible.
|
|
312
|
-
Traversal does not detect cycles. `diff` uses `each` with `root: true` and a `descend`
|
|
313
|
-
predicate that stops at arrays, non-plain objects, and branches replaced as a whole.
|
|
314
|
-
|
|
315
|
-
## map
|
|
316
|
-
|
|
317
|
-
Walk through an object and build a new result.
|
|
318
|
-
|
|
319
|
-
```ts
|
|
320
|
-
const result = map(data, (key, value, store, path) => {
|
|
133
|
+
const cleaned = map({ name: ' Natalia ', tags: [' ts '] }, (_key, value, store, path) => {
|
|
321
134
|
if (typeof value === 'string') {
|
|
322
135
|
set(store, path, value.trim())
|
|
323
136
|
}
|
|
324
137
|
})
|
|
138
|
+
// { name: 'Natalia', tags: ['ts'] }
|
|
325
139
|
```
|
|
326
140
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
- `key`: the current object key or array index
|
|
330
|
-
- `value`: the current value
|
|
331
|
-
- `store`: the result object being built
|
|
332
|
-
- `path`: the full path to the current value
|
|
141
|
+
`map` returns its store and ignores callback return values. Both walkers expect
|
|
142
|
+
acyclic data.
|
|
333
143
|
|
|
334
|
-
|
|
144
|
+
## Merge defaults or make a copy
|
|
335
145
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
Parse JSON or accept a ready value, and serialize values with optional error handling.
|
|
339
|
-
Exceptions return `undefined`, unless a fallback is provided or `onError` throws.
|
|
146
|
+
Combine nested settings while keeping the original defaults:
|
|
340
147
|
|
|
341
148
|
```ts
|
|
342
|
-
import {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const text = stringify(value, { fallback: 'null', space: 2 })
|
|
351
|
-
const object = { a: 1 }
|
|
352
|
-
const array = [1, 2]
|
|
353
|
-
|
|
354
|
-
parse(object) === object // true
|
|
355
|
-
parse(array) === array // true
|
|
356
|
-
|
|
357
|
-
parse('[]') // []
|
|
358
|
-
stringify({}) // '{}'
|
|
359
|
-
stringify([]) // '[]'
|
|
360
|
-
stringify(null) // 'null'
|
|
361
|
-
stringify('{}') // '"{}"'
|
|
149
|
+
import { clone, merge } from 'objwalk'
|
|
150
|
+
|
|
151
|
+
const defaults = { theme: { mode: 'light', contrast: 1 }, tags: ['default'] }
|
|
152
|
+
const settings = merge(clone(defaults), {
|
|
153
|
+
theme: { mode: 'dark' },
|
|
154
|
+
tags: ['custom'],
|
|
155
|
+
})
|
|
156
|
+
// { theme: { mode: 'dark', contrast: 1 }, tags: ['custom'] }
|
|
362
157
|
```
|
|
363
158
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
Validate the result before using it as a specific type. There is no `parse<T>()` overload.
|
|
368
|
-
|
|
369
|
-
`parse` accepts `unknown`. Plain objects (with `Object.prototype` or a `null` prototype) and
|
|
370
|
-
arrays are returned by reference, without cloning, serialization, mutation, or calling `reviver`.
|
|
371
|
-
Other inputs use `JSON.parse(typeof raw === 'string' ? raw : String(raw), reviver)`.
|
|
372
|
-
ArrayBuffer, DataView, and typed-array views use this text path; there is no binary decoder.
|
|
373
|
-
For example, a Node.js Buffer containing JSON text can be parsed through its native string conversion.
|
|
374
|
-
Errors during input recognition or string conversion follow the same `onError`/`fallback` contract.
|
|
375
|
-
|
|
376
|
-
| Option | `ParseOptions` | `StringifyOptions` |
|
|
377
|
-
| --- | --- | --- |
|
|
378
|
-
| `fallback` | `unknown`, default `undefined` | `string`, default `undefined` |
|
|
379
|
-
| `onError` | `(error: unknown) => void` | `(error: unknown) => void` |
|
|
380
|
-
| `reviver` | Native `JSON.parse` callback, only for the text path | — |
|
|
381
|
-
| `replacer` | — | Native callback or array of strings/numbers (also accepts `null`) |
|
|
382
|
-
| `space` | — | `string` or `number` for native JSON formatting |
|
|
383
|
-
|
|
384
|
-
Callbacks retain native `this` binding. For example, a reviver can transform or delete properties,
|
|
385
|
-
and a replacer can select or transform values:
|
|
159
|
+
`merge` combines plain objects and replaces arrays. `clone` copies nested objects
|
|
160
|
+
and arrays, including circular references, with support for `Date` and `RegExp`.
|
|
161
|
+
See the [copying contract](SPEC.md#clone) for supported types.
|
|
386
162
|
|
|
387
|
-
|
|
388
|
-
parse('{"count":2,"secret":true}', {
|
|
389
|
-
reviver: (key, value) => key === 'secret' ? undefined : value,
|
|
390
|
-
}) // { count: 2 }
|
|
163
|
+
## Handle JSON at the boundary
|
|
391
164
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
On an exception, `onError` receives the original error once, then the function returns `fallback`.
|
|
397
|
-
The callback's return value is ignored. Exceptions from `reviver`, `replacer`, `toJSON`, or getters
|
|
398
|
-
follow the same rule. If `onError` throws, its exception propagates to the caller:
|
|
165
|
+
`parse` accepts JSON text or a ready object or array. `stringify` serializes a value.
|
|
166
|
+
Use a fallback when an operation fails:
|
|
399
167
|
|
|
400
168
|
```ts
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}) //
|
|
406
|
-
|
|
407
|
-
stringify(1n, { fallback: 'null' }) // 'null' (BigInt throws natively)
|
|
408
|
-
stringify(undefined, { fallback: 'null' }) // undefined (no exception)
|
|
409
|
-
parse('false', { fallback: true }) // false
|
|
410
|
-
parse('null', { fallback: {} }) // null
|
|
169
|
+
import { parse, stringify } from 'objwalk'
|
|
170
|
+
|
|
171
|
+
parse('{"enabled":true}') // { enabled: true }
|
|
172
|
+
parse({ enabled: true }) // { enabled: true }, same object
|
|
173
|
+
parse('invalid JSON', { fallback: null }) // null
|
|
174
|
+
stringify({ enabled: true }) // '{"enabled":true}'
|
|
411
175
|
```
|
|
412
176
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
is preserved. Circular references throw natively and use the same error handling as BigInt.
|
|
418
|
-
The wrappers do not mutate inputs, undo callback side effects, log errors, or repair invalid JSON.
|
|
177
|
+
Without a fallback, errors return `undefined`. Native `reviver`, `replacer`,
|
|
178
|
+
formatting, and an `onError` callback are available through options.
|
|
179
|
+
`parse` returns `unknown`; validate the result before using it as a domain type.
|
|
180
|
+
[JSON options and error handling](SPEC.md#parse-and-stringify).
|
|
419
181
|
|
|
420
|
-
##
|
|
182
|
+
## API
|
|
421
183
|
|
|
422
|
-
|
|
|
184
|
+
| Task | Function |
|
|
423
185
|
| --- | --- |
|
|
424
|
-
|
|
|
425
|
-
|
|
|
426
|
-
|
|
|
427
|
-
|
|
|
428
|
-
|
|
|
186
|
+
| Read a value or select keys | [`pick(data, path?, options?)`](SPEC.md#pick) |
|
|
187
|
+
| Write a value by path | [`set(data, path, value)`](SPEC.md#set) |
|
|
188
|
+
| Delete one or several paths | [`flush(data, path)`](SPEC.md#flush) |
|
|
189
|
+
| Reshape data with a mapping | [`extract(source, mapping)`](SPEC.md#extract) |
|
|
190
|
+
| Visit nested values | [`each(data, callback, options?)`](SPEC.md#each) |
|
|
191
|
+
| Build a result while walking | [`map(data, callback, store?)`](SPEC.md#map) |
|
|
192
|
+
| Merge plain objects | [`merge(target, ...sources)`](SPEC.md#merge) |
|
|
193
|
+
| Copy supported values | [`clone(value)`](SPEC.md#clone) |
|
|
194
|
+
| Compare values or selected paths | [`equally(a, b, paths?)`](SPEC.md#equally) |
|
|
195
|
+
| Describe changes between values | [`diff(first, last)`](SPEC.md#diff) |
|
|
196
|
+
| Parse JSON or accept a ready value | [`parse(raw, options?)`](SPEC.md#parse-and-stringify) |
|
|
197
|
+
| Serialize a value as JSON | [`stringify(value, options?)`](SPEC.md#parse-and-stringify) |
|
|
198
|
+
|
|
199
|
+
For exact types, mutation rules, and edge cases, use the [full specification](SPEC.md).
|
|
429
200
|
|
|
430
201
|
## Agent skill
|
|
431
202
|
|
|
432
|
-
The package includes an [objwalk skill](skills/objwalk/SKILL.md)
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
The skill explains the existing object utilities and JSON helpers, and directs agents to use
|
|
437
|
-
them without redundant wrappers that only repeat object checks.
|
|
438
|
-
|
|
439
|
-
## API
|
|
440
|
-
|
|
441
|
-
```ts
|
|
442
|
-
pick(data, path)
|
|
443
|
-
set(data, path, value)
|
|
444
|
-
flush(data, path)
|
|
445
|
-
extract(data, mapping)
|
|
446
|
-
each(data, callback, options?)
|
|
447
|
-
map(data, callback)
|
|
448
|
-
merge(target, ...sources)
|
|
449
|
-
equally(a, b)
|
|
450
|
-
diff(first, last) // DiffOperation[]
|
|
451
|
-
parse(raw, options?) // unknown
|
|
452
|
-
stringify(value, options?) // string | undefined
|
|
453
|
-
```
|
|
203
|
+
The package includes an [objwalk skill](skills/objwalk/SKILL.md). Point your agent at
|
|
204
|
+
`node_modules/objwalk/skills/objwalk/SKILL.md`, or link the entire `skills/objwalk`
|
|
205
|
+
folder into its skill directory.
|