objwalk 1.6.0 → 1.8.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 +137 -369
- package/SPEC.md +442 -0
- package/dist/index.cjs +113 -106
- package/dist/index.d.cts +21 -6
- package/dist/index.d.ts +21 -6
- package/dist/index.js +113 -106
- package/package.json +3 -2
- package/skills/objwalk/SKILL.md +50 -9
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,203 @@ 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.
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
const data = {
|
|
27
|
-
user: {
|
|
28
|
-
name: 'Natalia Romanoff',
|
|
29
|
-
email: 'black.widow@example.com',
|
|
30
|
-
},
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
pick(data, 'user.name')
|
|
34
|
-
// 'Natalia Romanoff'
|
|
35
|
-
```
|
|
36
|
-
|
|
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.
|
|
22
|
+
Import the tools you need and use paths to reach nested values:
|
|
45
23
|
|
|
46
24
|
```ts
|
|
47
|
-
pick
|
|
48
|
-
// ['name', 'email']
|
|
49
|
-
```
|
|
25
|
+
import { pick, set, flush } from 'objwalk'
|
|
50
26
|
|
|
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' } }
|
|
108
|
-
|
|
109
|
-
flush({}, 'path.to.key') // {}
|
|
110
|
-
flush({ user: null }, 'user.name') // { user: null }
|
|
111
|
-
```
|
|
64
|
+
import { extract } from 'objwalk'
|
|
112
65
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
```ts
|
|
151
|
-
import { diff, type DiffOperation } from 'objwalk'
|
|
94
|
+
equally(before, after) // false
|
|
95
|
+
equally(before, after, ['user.name']) // true
|
|
152
96
|
|
|
153
|
-
|
|
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
|
-
`
|
|
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.
|
|
209
107
|
|
|
210
|
-
##
|
|
108
|
+
## Walk objects and arrays
|
|
211
109
|
|
|
212
|
-
|
|
110
|
+
`each` visits nested values and tells you where they live:
|
|
213
111
|
|
|
214
112
|
```ts
|
|
215
|
-
|
|
216
|
-
profile: {
|
|
217
|
-
fullName: 'Natalia Romanoff',
|
|
218
|
-
email: 'black.widow@example.com',
|
|
219
|
-
},
|
|
220
|
-
}
|
|
113
|
+
import { each } from 'objwalk'
|
|
221
114
|
|
|
222
|
-
|
|
223
|
-
'
|
|
224
|
-
|
|
115
|
+
each({ user: { name: 'Natalia' }, tags: ['ts'] }, (_key, value, path) => {
|
|
116
|
+
if (typeof value === 'string') {
|
|
117
|
+
console.log(path, value)
|
|
118
|
+
}
|
|
225
119
|
})
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
// name: 'Natalia Romanoff',
|
|
229
|
-
// email: 'black.widow@example.com',
|
|
230
|
-
// },
|
|
231
|
-
// }
|
|
120
|
+
// user.name Natalia
|
|
121
|
+
// tags[0] ts
|
|
232
122
|
```
|
|
233
123
|
|
|
234
|
-
|
|
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).
|
|
235
127
|
|
|
236
|
-
|
|
237
|
-
extract(source, {
|
|
238
|
-
'user.name': ['profile.displayName', 'profile.fullName'],
|
|
239
|
-
})
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
Use `[n]` to map over arrays.
|
|
128
|
+
Use `map` to transform values while keeping the object and array structure:
|
|
243
129
|
|
|
244
130
|
```ts
|
|
245
|
-
|
|
246
|
-
products: [
|
|
247
|
-
{ name: 'Keyboard' },
|
|
248
|
-
{ name: 'Mouse' },
|
|
249
|
-
],
|
|
250
|
-
}
|
|
131
|
+
import { map } from 'objwalk'
|
|
251
132
|
|
|
252
|
-
|
|
253
|
-
'
|
|
254
|
-
|
|
255
|
-
// {
|
|
256
|
-
// items: [
|
|
257
|
-
// { title: 'Keyboard' },
|
|
258
|
-
// { title: 'Mouse' },
|
|
259
|
-
// ],
|
|
260
|
-
// }
|
|
133
|
+
const cleaned = map({ name: ' Natalia ', tags: [' ts '] }, (_key, value) =>
|
|
134
|
+
typeof value === 'string' ? value.trim() : value
|
|
135
|
+
)
|
|
136
|
+
// { name: 'Natalia', tags: ['ts'] }
|
|
261
137
|
```
|
|
262
138
|
|
|
263
|
-
|
|
139
|
+
Each callback return becomes the value at that key or index in the new result.
|
|
140
|
+
The input is not modified by `map`. Both walkers expect acyclic data unless
|
|
141
|
+
traversal options prune the cycles.
|
|
264
142
|
|
|
265
|
-
|
|
143
|
+
When your callback handles each entry as a whole, use a shallow walk:
|
|
266
144
|
|
|
267
145
|
```ts
|
|
268
|
-
|
|
269
|
-
console.log(key, value, path, segments)
|
|
270
|
-
})
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
The callback receives:
|
|
146
|
+
import { map } from 'objwalk'
|
|
274
147
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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:
|
|
286
|
-
|
|
287
|
-
```ts
|
|
288
|
-
each(data, (key, value, path, segments) => {
|
|
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
|
-
})
|
|
148
|
+
const counts = map(
|
|
149
|
+
{ editors: ['Natalia'], readers: ['Ada', 'Grace'] },
|
|
150
|
+
(_key, value) => value.length,
|
|
151
|
+
{ shallow: true }
|
|
152
|
+
)
|
|
153
|
+
// { editors: 1, readers: 2 }
|
|
295
154
|
```
|
|
296
155
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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.
|
|
156
|
+
Shallow dictionary keys are typed as `string`; shallow array indexes are `number`.
|
|
157
|
+
For selective traversal, pass `descend` in the third argument.
|
|
158
|
+
[Mapping options](SPEC.md#map).
|
|
307
159
|
|
|
308
|
-
|
|
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.
|
|
160
|
+
## Merge defaults or make a copy
|
|
314
161
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
Walk through an object and build a new result.
|
|
162
|
+
Combine nested settings while keeping the original defaults:
|
|
318
163
|
|
|
319
164
|
```ts
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
165
|
+
import { clone, merge } from 'objwalk'
|
|
166
|
+
|
|
167
|
+
const defaults = { theme: { mode: 'light', contrast: 1 }, tags: ['default'] }
|
|
168
|
+
const settings = merge(clone(defaults), {
|
|
169
|
+
theme: { mode: 'dark' },
|
|
170
|
+
tags: ['custom'],
|
|
324
171
|
})
|
|
172
|
+
// { theme: { mode: 'dark', contrast: 1 }, tags: ['custom'] }
|
|
325
173
|
```
|
|
326
174
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
- `value`: the current value
|
|
331
|
-
- `store`: the result object being built
|
|
332
|
-
- `path`: the full path to the current value
|
|
333
|
-
|
|
334
|
-
`map` returns `store`.
|
|
335
|
-
|
|
336
|
-
## parse and stringify
|
|
175
|
+
`merge` combines plain objects and replaces arrays. `clone` copies nested objects
|
|
176
|
+
and arrays, including circular references, with support for `Date` and `RegExp`.
|
|
177
|
+
See the [copying contract](SPEC.md#clone) for supported types.
|
|
337
178
|
|
|
338
|
-
|
|
339
|
-
Exceptions return `undefined`, unless a fallback is provided or `onError` throws.
|
|
179
|
+
## Handle JSON at the boundary
|
|
340
180
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
// Signatures:
|
|
345
|
-
// parse(raw: unknown, options?: ParseOptions): unknown
|
|
346
|
-
// stringify(value: unknown, options?: StringifyOptions): string | undefined
|
|
347
|
-
|
|
348
|
-
const value = parse('{"a":1}') // { a: 1 }, typed as unknown
|
|
349
|
-
const valueOrNull = parse('{', { fallback: null }) // null
|
|
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('{}') // '"{}"'
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
Root values can be objects, arrays, strings, numbers, booleans, or `null`;
|
|
365
|
-
neither function requires an expected shape or restricts values to objects.
|
|
366
|
-
`parse` returns `unknown`; successful parsing does not validate the expected data structure.
|
|
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:
|
|
181
|
+
`parse` accepts JSON text or a ready object or array. `stringify` serializes a value.
|
|
182
|
+
Use a fallback when an operation fails:
|
|
386
183
|
|
|
387
184
|
```ts
|
|
388
|
-
parse
|
|
389
|
-
reviver: (key, value) => key === 'secret' ? undefined : value,
|
|
390
|
-
}) // { count: 2 }
|
|
185
|
+
import { parse, stringify } from 'objwalk'
|
|
391
186
|
|
|
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:
|
|
399
|
-
|
|
400
|
-
```ts
|
|
401
|
-
const requiredText = stringify(value, {
|
|
402
|
-
onError: (error) => {
|
|
403
|
-
throw error
|
|
404
|
-
},
|
|
405
|
-
}) // still string | undefined
|
|
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
|
|
187
|
+
parse('{"enabled":true}') // { enabled: true }
|
|
188
|
+
parse({ enabled: true }) // { enabled: true }, same object
|
|
189
|
+
parse('invalid JSON', { fallback: null }) // null
|
|
190
|
+
stringify({ enabled: true }) // '{"enabled":true}'
|
|
411
191
|
```
|
|
412
192
|
|
|
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.
|
|
193
|
+
Without a fallback, errors return `undefined`. Native `reviver`, `replacer`,
|
|
194
|
+
formatting, and an `onError` callback are available through options.
|
|
195
|
+
`parse` returns `unknown`; validate the result before using it as a domain type.
|
|
196
|
+
[JSON options and error handling](SPEC.md#parse-and-stringify).
|
|
419
197
|
|
|
420
|
-
##
|
|
198
|
+
## API
|
|
421
199
|
|
|
422
|
-
|
|
|
200
|
+
| Task | Function |
|
|
423
201
|
| --- | --- |
|
|
424
|
-
|
|
|
425
|
-
|
|
|
426
|
-
|
|
|
427
|
-
|
|
|
428
|
-
|
|
|
202
|
+
| Read a value or select keys | [`pick(data, path?, options?)`](SPEC.md#pick) |
|
|
203
|
+
| Write a value by path | [`set(data, path, value)`](SPEC.md#set) |
|
|
204
|
+
| Delete one or several paths | [`flush(data, path)`](SPEC.md#flush) |
|
|
205
|
+
| Reshape data with a mapping | [`extract(source, mapping)`](SPEC.md#extract) |
|
|
206
|
+
| Visit nested values | [`each(data, callback, options?)`](SPEC.md#each) |
|
|
207
|
+
| Transform values while keeping keys | [`map(data, callback, options?)`](SPEC.md#map) |
|
|
208
|
+
| Merge plain objects | [`merge(target, ...sources)`](SPEC.md#merge) |
|
|
209
|
+
| Copy supported values | [`clone(value)`](SPEC.md#clone) |
|
|
210
|
+
| Compare values or selected paths | [`equally(a, b, paths?)`](SPEC.md#equally) |
|
|
211
|
+
| Describe changes between values | [`diff(first, last)`](SPEC.md#diff) |
|
|
212
|
+
| Parse JSON or accept a ready value | [`parse(raw, options?)`](SPEC.md#parse-and-stringify) |
|
|
213
|
+
| Serialize a value as JSON | [`stringify(value, options?)`](SPEC.md#parse-and-stringify) |
|
|
214
|
+
|
|
215
|
+
For exact types, mutation rules, and edge cases, use the [full specification](SPEC.md).
|
|
429
216
|
|
|
430
217
|
## Agent skill
|
|
431
218
|
|
|
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
|
-
```
|
|
219
|
+
The package includes an [objwalk skill](skills/objwalk/SKILL.md). Point your agent at
|
|
220
|
+
`node_modules/objwalk/skills/objwalk/SKILL.md`, or link the entire `skills/objwalk`
|
|
221
|
+
folder into its skill directory.
|