objwalk 1.7.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 CHANGED
@@ -125,21 +125,37 @@ Return `false` to stop the entire walk. For more control, `each` also accepts
125
125
  `root` and `descend` options and provides literal path segments as a fourth
126
126
  callback argument. [Traversal details](SPEC.md#each).
127
127
 
128
- Use `map` when the walk should build a result:
128
+ Use `map` to transform values while keeping the object and array structure:
129
129
 
130
130
  ```ts
131
- import { map, set } from 'objwalk'
131
+ import { map } from 'objwalk'
132
132
 
133
- const cleaned = map({ name: ' Natalia ', tags: [' ts '] }, (_key, value, store, path) => {
134
- if (typeof value === 'string') {
135
- set(store, path, value.trim())
136
- }
137
- })
133
+ const cleaned = map({ name: ' Natalia ', tags: [' ts '] }, (_key, value) =>
134
+ typeof value === 'string' ? value.trim() : value
135
+ )
138
136
  // { name: 'Natalia', tags: ['ts'] }
139
137
  ```
140
138
 
141
- `map` returns its store and ignores callback return values. Both walkers expect
142
- acyclic data.
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.
142
+
143
+ When your callback handles each entry as a whole, use a shallow walk:
144
+
145
+ ```ts
146
+ import { map } from 'objwalk'
147
+
148
+ const counts = map(
149
+ { editors: ['Natalia'], readers: ['Ada', 'Grace'] },
150
+ (_key, value) => value.length,
151
+ { shallow: true }
152
+ )
153
+ // { editors: 1, readers: 2 }
154
+ ```
155
+
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).
143
159
 
144
160
  ## Merge defaults or make a copy
145
161
 
@@ -188,7 +204,7 @@ formatting, and an `onError` callback are available through options.
188
204
  | Delete one or several paths | [`flush(data, path)`](SPEC.md#flush) |
189
205
  | Reshape data with a mapping | [`extract(source, mapping)`](SPEC.md#extract) |
190
206
  | Visit nested values | [`each(data, callback, options?)`](SPEC.md#each) |
191
- | Build a result while walking | [`map(data, callback, store?)`](SPEC.md#map) |
207
+ | Transform values while keeping keys | [`map(data, callback, options?)`](SPEC.md#map) |
192
208
  | Merge plain objects | [`merge(target, ...sources)`](SPEC.md#merge) |
193
209
  | Copy supported values | [`clone(value)`](SPEC.md#clone) |
194
210
  | Compare values or selected paths | [`equally(a, b, paths?)`](SPEC.md#equally) |
package/SPEC.md CHANGED
@@ -30,7 +30,7 @@ type DiffOperation =
30
30
  | `flush(data, path)` | Input object | Mutates the input |
31
31
  | `extract(source, mapping)` | New result object | Selected values are not cloned |
32
32
  | `each(data, callback, options?)` | `undefined` | Passes original values to callbacks |
33
- | `map(data, callback, store?)` | Supplied store or a new object | Callback builds or mutates the store |
33
+ | `map(data, callback, options?)` | New array or dictionary | Callback returns each replacement value; pruned and returned values can retain references |
34
34
  | `merge(target, ...sources)` | Target object | Mutates the target; replacement values are not cloned |
35
35
  | `clone(value)` | Value of the same declared generic type | Recursively copies supported objects and arrays |
36
36
  | `equally(a, b, paths?)` | `boolean` | Does not assign to the inputs |
@@ -41,8 +41,8 @@ type DiffOperation =
41
41
  `pick`, `set`, and `flush` use `{}` when their `data` argument is `undefined`.
42
42
  `set` and `flush` still require the path argument. `pick` defaults its path to `''`.
43
43
  Their declared data contract is `ObjwalkObject`; this is not a guarantee that all
44
- functions accept every possible JavaScript value. `each` and `map` accept `any`;
45
- `clone` is generic; `equally`, `diff`, `parse`, and `stringify` accept `unknown` inputs.
44
+ functions accept every possible JavaScript value. `each` accepts `any`; `map`
45
+ accepts dictionaries and readonly arrays; `clone` is generic; `equally`, `diff`, `parse`, and `stringify` accept `unknown` inputs.
46
46
  `extract` requires an object and a mapping; `merge` requires an object target.
47
47
 
48
48
  Except for the documented JSON error handling, exceptions from callbacks, getters,
@@ -217,22 +217,81 @@ not share stop state.
217
217
  ## map
218
218
 
219
219
  ```ts
220
- type MapCallback<TStore extends Record<string, any> = Record<string, any>> = (
221
- key: WalkKey,
220
+ type MapCallback<TResult = unknown, TKey extends WalkKey = WalkKey> = (
221
+ key: TKey,
222
222
  value: any,
223
- store: TStore,
224
223
  path: string
225
- ) => void
226
- ```
224
+ ) => TResult
227
225
 
228
- `map(data, callback, store?)` uses the default `each` traversal for objects and arrays,
229
- excluding the root. The callback receives the original value and the same store
230
- on every visit. A supplied store is returned by identity; otherwise the store is
231
- a new `{}`. The generic store type is preserved in the return type.
226
+ type MapOptions = {
227
+ descend?: EachOptions['descend']
228
+ shallow?: boolean
229
+ }
230
+ ```
232
231
 
233
- All callback return values, including `false`, are ignored. `map` exposes neither
234
- root/descend options nor literal segments. Errors propagate and completed store
235
- mutations remain applied. Cycles are not detected.
232
+ `map(data, callback, options?)` transforms a dictionary or array without assigning
233
+ into the input. An array root produces a new array; a dictionary root produces a
234
+ new ordinary object. The root is excluded from callback visits. Every visited
235
+ property or array index retains its literal key; its value becomes the callback's
236
+ return value. `false`, `null`, and `undefined` are stored as ordinary results;
237
+ there is no callback return value for stopping, filtering, or deleting entries.
238
+ Callback replacements are not traversed again, including returned objects and arrays.
239
+
240
+ The callback receives `(key, value, path)`. Object keys are strings, array indexes
241
+ are numbers, and paths follow `each`'s descriptive string format. No accumulator or
242
+ store argument exists. `MapCallback`'s first generic argument is the return value
243
+ rather than the former store type; the optional second argument is the key type.
244
+ Array results are typed as `TResult[]`; dictionary results as
245
+ `Record<string, TResult>`. A dictionary-or-array union input produces the matching
246
+ union result type. Readonly input arrays are accepted and produce mutable new arrays.
247
+ Deep callback values are typed as `any`, since earlier child transformations can
248
+ change their contents.
249
+
250
+ Deep traversal is the default. Plain objects and arrays are recursively mapped
251
+ before their containing value is passed to the callback. Thus callbacks run in
252
+ postorder: children first, then their parent, with the root still excluded.
253
+ A container callback receives a newly built container containing its already
254
+ mapped children. A pruned container callback instead receives the original value
255
+ by reference. `Date`, `RegExp`, other non-plain objects, and functions are leaves:
256
+ they are passed directly to the callback without rebuilding their internal state.
257
+ The root dictionary's own entries are enumerated regardless of its prototype;
258
+ the plain-object restriction applies to nested traversal.
259
+
260
+ Objects enumerate own enumerable string keys; inherited, symbol, and non-enumerable
261
+ properties are excluded. Literal dots, brackets, empty keys, and `__proto__` are
262
+ preserved as own data properties rather than parsed as paths. Result objects use
263
+ `Object.prototype`; source prototypes and descriptors are not copied. Array indexes
264
+ follow `each` iteration, preserving length and absent slots. An explicitly returned
265
+ `undefined` is an own slot, whereas an unvisited hole remains absent. Inherited
266
+ indexed values can be visited; extra array properties are excluded.
267
+
268
+ `descend` runs on each original traversable container before visiting children,
269
+ including the excluded root. It receives `EachEntry` with literal `segments`;
270
+ modifying these segments does not alter traversal paths. Returning `false` prunes
271
+ children, then the container's callback processes the original value as a whole.
272
+ Pruning the root produces an empty container of the same kind, with no callback
273
+ visits. This timing differs from `each`, which calls its callback before `descend`.
274
+
275
+ `shallow` defaults to `false`. With literal `shallow: true`, only immediate root
276
+ children are mapped: dictionary callback keys are typed as `string`, and statically
277
+ known array keys as `number`. Deep calls, dynamic shallow flags, and union inputs
278
+ keep `WalkKey`. An arbitrary `descend` predicate does not narrow keys. When both
279
+ options are present, `descend` is evaluated only for the root, because child
280
+ containers are already at the shallow limit.
281
+
282
+ Traversed containers are new. Pruned containers, special objects, and values
283
+ returned by the callback may share references with the input. Each occurrence of
284
+ a shared traversable container is mapped separately. Cycles are not detected;
285
+ pruning can prevent descent into a cyclic branch. Exceptions from callbacks,
286
+ getters, and `descend` propagate. The function does not roll back callback side
287
+ effects or await promises returned by callbacks.
288
+
289
+ This replaces the published accumulator-based contract. Migrate
290
+ `map(data, (key, value, store, path) => { store[key] = transform(value) }, store)`
291
+ to `map(data, (key, value, path) => transform(value), options)` when the result
292
+ retains input keys. Callers that build a different structure should use `each`
293
+ and manage their accumulator explicitly. Old stores are not accepted as options,
294
+ and there is no compatibility overload or `undefined` placeholder.
236
295
 
237
296
  ## merge
238
297
 
package/dist/index.cjs CHANGED
@@ -225,14 +225,6 @@ var extract = (source, mapping) => {
225
225
  return result;
226
226
  };
227
227
 
228
- // src/map.ts
229
- var map = (data, callback, store = {}) => {
230
- each(data, (key, value, path) => {
231
- callback(key, value, store, path);
232
- });
233
- return store;
234
- };
235
-
236
228
  // src/helpers/isPlainObject.ts
237
229
  var isPlainObject = (value) => {
238
230
  if (value !== null && typeof value === "object") {
@@ -242,6 +234,30 @@ var isPlainObject = (value) => {
242
234
  return false;
243
235
  };
244
236
 
237
+ // src/map.ts
238
+ var map = (data, callback, { descend, shallow = false } = {}) => {
239
+ const walk = (value, key, path, segments) => {
240
+ const root = key === void 0;
241
+ let mapped = root ? Array.isArray(value) ? [] : {} : value;
242
+ if ((root || Array.isArray(value) || isPlainObject(value)) && (root || shallow === false) && (descend === void 0 || descend({ key, value, path, segments: [...segments] }))) {
243
+ mapped = Array.isArray(value) ? Array(value.length) : {};
244
+ each(value, (key2, value2) => {
245
+ const childPath = createPath(path, key2, Array.isArray(mapped));
246
+ const childSegments = [...segments, key2];
247
+ const result = walk(value2, key2, childPath, childSegments);
248
+ Object.defineProperty(mapped, key2, {
249
+ value: result,
250
+ writable: true,
251
+ enumerable: true,
252
+ configurable: true
253
+ });
254
+ }, { descend: ({ segments: segments2 }) => segments2.length === 0 });
255
+ }
256
+ return key !== void 0 ? callback(key, mapped, path) : mapped;
257
+ };
258
+ return walk(data, void 0, "", []);
259
+ };
260
+
245
261
  // src/merge.ts
246
262
  var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
247
263
  var merge = (target, ...sources) => {
package/dist/index.d.cts CHANGED
@@ -48,8 +48,23 @@ declare const pick: (data?: ObjwalkObject$1, path?: string, options?: PickOption
48
48
  type ExtractMapping = Record<string, string | string[]>;
49
49
  declare const extract: (source: ObjwalkObject$1, mapping: ExtractMapping) => ObjwalkObject$1;
50
50
 
51
- type MapCallback<TStore extends Record<string, any> = Record<string, any>> = (key: WalkKey, value: any, store: TStore, path: string) => void;
52
- declare const map: <TStore extends Record<string, any> = Record<string, any>>(data: any, callback: MapCallback<TStore>, store?: TStore) => TStore;
51
+ type MapCallback<TResult = unknown, TKey extends WalkKey = WalkKey> = (key: TKey, value: any, path: string) => TResult;
52
+ type MapOptions = {
53
+ descend?: EachOptions['descend'];
54
+ shallow?: boolean;
55
+ };
56
+ type Map = {
57
+ <TResult>(data: readonly unknown[], callback: MapCallback<TResult, number>, options: MapOptions & {
58
+ shallow: true;
59
+ }): TResult[];
60
+ <TResult>(data: Record<string, unknown>, callback: MapCallback<TResult, string>, options: MapOptions & {
61
+ shallow: true;
62
+ }): Record<string, TResult>;
63
+ <TResult>(data: readonly unknown[], callback: MapCallback<TResult>, options?: MapOptions): TResult[];
64
+ <TResult>(data: Record<string, unknown>, callback: MapCallback<TResult>, options?: MapOptions): Record<string, TResult>;
65
+ <TResult>(data: Record<string, unknown> | readonly unknown[], callback: MapCallback<TResult>, options?: MapOptions): Record<string, TResult> | TResult[];
66
+ };
67
+ declare const map: Map;
53
68
 
54
69
  declare const merge: <T extends ObjwalkObject$1>(target: T, ...sources: unknown[]) => T;
55
70
 
@@ -68,4 +83,4 @@ declare const parse: (raw: unknown, { fallback, onError, reviver }?: ParseOption
68
83
 
69
84
  declare const stringify: (value: unknown, { fallback, onError, replacer, space }?: StringifyOptions) => string | undefined;
70
85
 
71
- export { type DiffOperation, type EachCallback, type EachEntry, type EachOptions, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type ParseOptions, type PickOptions, type StringifyOptions, type WalkKey, clone, diff, each, equally, extract, flush, map, merge, parse, pick, set, stringify };
86
+ export { type DiffOperation, type EachCallback, type EachEntry, type EachOptions, type ExtractMapping, type MapCallback, type MapOptions, type ObjwalkObject$1 as ObjwalkObject, type ParseOptions, type PickOptions, type StringifyOptions, type WalkKey, clone, diff, each, equally, extract, flush, map, merge, parse, pick, set, stringify };
package/dist/index.d.ts CHANGED
@@ -48,8 +48,23 @@ declare const pick: (data?: ObjwalkObject$1, path?: string, options?: PickOption
48
48
  type ExtractMapping = Record<string, string | string[]>;
49
49
  declare const extract: (source: ObjwalkObject$1, mapping: ExtractMapping) => ObjwalkObject$1;
50
50
 
51
- type MapCallback<TStore extends Record<string, any> = Record<string, any>> = (key: WalkKey, value: any, store: TStore, path: string) => void;
52
- declare const map: <TStore extends Record<string, any> = Record<string, any>>(data: any, callback: MapCallback<TStore>, store?: TStore) => TStore;
51
+ type MapCallback<TResult = unknown, TKey extends WalkKey = WalkKey> = (key: TKey, value: any, path: string) => TResult;
52
+ type MapOptions = {
53
+ descend?: EachOptions['descend'];
54
+ shallow?: boolean;
55
+ };
56
+ type Map = {
57
+ <TResult>(data: readonly unknown[], callback: MapCallback<TResult, number>, options: MapOptions & {
58
+ shallow: true;
59
+ }): TResult[];
60
+ <TResult>(data: Record<string, unknown>, callback: MapCallback<TResult, string>, options: MapOptions & {
61
+ shallow: true;
62
+ }): Record<string, TResult>;
63
+ <TResult>(data: readonly unknown[], callback: MapCallback<TResult>, options?: MapOptions): TResult[];
64
+ <TResult>(data: Record<string, unknown>, callback: MapCallback<TResult>, options?: MapOptions): Record<string, TResult>;
65
+ <TResult>(data: Record<string, unknown> | readonly unknown[], callback: MapCallback<TResult>, options?: MapOptions): Record<string, TResult> | TResult[];
66
+ };
67
+ declare const map: Map;
53
68
 
54
69
  declare const merge: <T extends ObjwalkObject$1>(target: T, ...sources: unknown[]) => T;
55
70
 
@@ -68,4 +83,4 @@ declare const parse: (raw: unknown, { fallback, onError, reviver }?: ParseOption
68
83
 
69
84
  declare const stringify: (value: unknown, { fallback, onError, replacer, space }?: StringifyOptions) => string | undefined;
70
85
 
71
- export { type DiffOperation, type EachCallback, type EachEntry, type EachOptions, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type ParseOptions, type PickOptions, type StringifyOptions, type WalkKey, clone, diff, each, equally, extract, flush, map, merge, parse, pick, set, stringify };
86
+ export { type DiffOperation, type EachCallback, type EachEntry, type EachOptions, type ExtractMapping, type MapCallback, type MapOptions, type ObjwalkObject$1 as ObjwalkObject, type ParseOptions, type PickOptions, type StringifyOptions, type WalkKey, clone, diff, each, equally, extract, flush, map, merge, parse, pick, set, stringify };
package/dist/index.js CHANGED
@@ -188,14 +188,6 @@ var extract = (source, mapping) => {
188
188
  return result;
189
189
  };
190
190
 
191
- // src/map.ts
192
- var map = (data, callback, store = {}) => {
193
- each(data, (key, value, path) => {
194
- callback(key, value, store, path);
195
- });
196
- return store;
197
- };
198
-
199
191
  // src/helpers/isPlainObject.ts
200
192
  var isPlainObject = (value) => {
201
193
  if (value !== null && typeof value === "object") {
@@ -205,6 +197,30 @@ var isPlainObject = (value) => {
205
197
  return false;
206
198
  };
207
199
 
200
+ // src/map.ts
201
+ var map = (data, callback, { descend, shallow = false } = {}) => {
202
+ const walk = (value, key, path, segments) => {
203
+ const root = key === void 0;
204
+ let mapped = root ? Array.isArray(value) ? [] : {} : value;
205
+ if ((root || Array.isArray(value) || isPlainObject(value)) && (root || shallow === false) && (descend === void 0 || descend({ key, value, path, segments: [...segments] }))) {
206
+ mapped = Array.isArray(value) ? Array(value.length) : {};
207
+ each(value, (key2, value2) => {
208
+ const childPath = createPath(path, key2, Array.isArray(mapped));
209
+ const childSegments = [...segments, key2];
210
+ const result = walk(value2, key2, childPath, childSegments);
211
+ Object.defineProperty(mapped, key2, {
212
+ value: result,
213
+ writable: true,
214
+ enumerable: true,
215
+ configurable: true
216
+ });
217
+ }, { descend: ({ segments: segments2 }) => segments2.length === 0 });
218
+ }
219
+ return key !== void 0 ? callback(key, mapped, path) : mapped;
220
+ };
221
+ return walk(data, void 0, "", []);
222
+ };
223
+
208
224
  // src/merge.ts
209
225
  var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
210
226
  var merge = (target, ...sources) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "objwalk",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Small utilities for reading, writing, walking, and reshaping nested JavaScript objects.",
5
5
  "keywords": [
6
6
  "object",
@@ -31,7 +31,7 @@ import { clone, diff, each, equally, extract, flush, map, merge, parse, pick, se
31
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
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
- | Build a store while walking | `map(data, (key, value, store, path) => { ... }, store?)`; returns the store, ignores callback return values. |
34
+ | Transform nested values | `map(data, (key, value, path) => replacement, options?)`; returns a new array or dictionary with the same keys. |
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
37
  | Deep comparison | `equally(a, b, paths?)`; supports `NaN`, `Date`, `RegExp`, and circular references; optional `string[]` compares only selected paths. |
@@ -108,11 +108,32 @@ the entire traversal, including siblings and ancestor branches; other return val
108
108
  are ignored. The stopped container's `descend` is not called, and its children and
109
109
  subsequent values are not read. Object keys are collected on entering a container;
110
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
111
+ that returned `false` must omit that return to keep walking. Mutating received
112
+ segments does not affect traversal. Callback/getter/descend errors
114
113
  propagate. Cycles are not detected.
115
114
 
115
+ `map(data, callback, options?)` returns a new array for an array root and a new
116
+ ordinary dictionary for a dictionary root. Its callback is `(key, value, path)`;
117
+ return the replacement value. There is no store argument. `false`, `null`, and
118
+ `undefined` are ordinary output values, not traversal control. To accumulate a
119
+ different structure, use `each` with an explicit accumulator.
120
+
121
+ Deep map callbacks run children first, then their containing value; the root is
122
+ excluded. Plain objects and arrays are rebuilt, while non-plain objects such as
123
+ Date and RegExp are passed to the callback as leaves. Callback results are not
124
+ traversed again. Source data is not assigned to, but returned or pruned values
125
+ can retain references. Array holes and literal object keys are preserved.
126
+
127
+ `MapOptions` is the third argument. `descend` receives original containers before
128
+ children are mapped; false prunes their children and passes the original value to
129
+ the callback. It also runs for the excluded root, where false produces an empty
130
+ result of the same kind. Literal `shallow: true` visits only immediate children,
131
+ typing dictionary keys as `string` and array keys as `number`. When both options
132
+ are present, `descend` is evaluated only for the root. Deep/dynamic calls keep
133
+ `WalkKey`; a custom descend predicate alone cannot narrow keys.
134
+ `MapCallback<TResult, TKey>` describes the callback's returned value and key type.
135
+ This is a breaking replacement of the old store-based map, not an overload.
136
+
116
137
  ## Differences
117
138
 
118
139
  `diff` recursively compares two plain objects by their own enumerable string keys;