objwalk 0.1.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 ADDED
@@ -0,0 +1,192 @@
1
+ # objwalk
2
+
3
+ Small utilities for reading, writing, walking, and reshaping nested JavaScript objects.
4
+
5
+ `objwalk` is built around a compact path syntax, so the same mental model works for picking values, setting values, extracting a new object shape, and walking through nested data.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install objwalk
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { each, extract, map, pick, set } from 'objwalk'
17
+ ```
18
+
19
+ ## pick
20
+
21
+ Read a value from an object by path.
22
+
23
+ ```ts
24
+ const data = {
25
+ user: {
26
+ name: 'Natalia Romanoff',
27
+ email: 'black.widow@example.com',
28
+ },
29
+ }
30
+
31
+ pick(data, 'user.name')
32
+ // 'Natalia Romanoff'
33
+ ```
34
+
35
+ Pick several keys from the same nested object.
36
+
37
+ ```ts
38
+ pick(data, 'user.{name,email}')
39
+ // { name: 'Natalia Romanoff', email: 'black.widow@example.com' }
40
+ ```
41
+
42
+ List keys from a nested object.
43
+
44
+ ```ts
45
+ pick(data, 'user.{*}')
46
+ // ['name', 'email']
47
+ ```
48
+
49
+ Read from arrays with an explicit index.
50
+
51
+ ```ts
52
+ const data = {
53
+ items: [{ title: 'First' }, { title: 'Second' }],
54
+ }
55
+
56
+ pick(data, 'items[1].title')
57
+ // 'Second'
58
+ ```
59
+
60
+ If the path cannot be resolved, `pick` returns `undefined`.
61
+
62
+ ## set
63
+
64
+ Set a value inside an object by path.
65
+
66
+ ```ts
67
+ const data = {}
68
+
69
+ set(data, 'user.name', 'Natalia')
70
+ // { user: { name: 'Natalia' } }
71
+ ```
72
+
73
+ Set values inside arrays with an explicit index.
74
+
75
+ ```ts
76
+ const data = {}
77
+
78
+ set(data, 'items[0].title', 'First')
79
+ // { items: [{ title: 'Romanoff' }] }
80
+ ```
81
+
82
+ `set` mutates the object passed to it and returns the same object.
83
+
84
+ ## extract
85
+
86
+ Create a new object by mapping destination paths to source paths.
87
+
88
+ ```ts
89
+ const source = {
90
+ profile: {
91
+ fullName: 'Natalia Romanoff',
92
+ email: 'black.widow@example.com',
93
+ },
94
+ }
95
+
96
+ extract(source, {
97
+ 'user.name': 'profile.fullName',
98
+ 'user.email': 'profile.email',
99
+ })
100
+ // {
101
+ // user: {
102
+ // name: 'Natalia Romanoff',
103
+ // email: 'black.widow@example.com',
104
+ // },
105
+ // }
106
+ ```
107
+
108
+ Use an array of source paths to provide fallbacks. The first resolved value is used.
109
+
110
+ ```ts
111
+ extract(source, {
112
+ 'user.name': ['profile.displayName', 'profile.fullName'],
113
+ })
114
+ ```
115
+
116
+ Use `[n]` to map over arrays.
117
+
118
+ ```ts
119
+ const source = {
120
+ products: [
121
+ { name: 'Keyboard' },
122
+ { name: 'Mouse' },
123
+ ],
124
+ }
125
+
126
+ extract(source, {
127
+ 'items[n].title': 'products[n].name',
128
+ })
129
+ // {
130
+ // items: [
131
+ // { title: 'Keyboard' },
132
+ // { title: 'Mouse' },
133
+ // ],
134
+ // }
135
+ ```
136
+
137
+ ## each
138
+
139
+ Walk through an object and call a function for each value.
140
+
141
+ ```ts
142
+ each(data, (key, value, path) => {
143
+ console.log(key, value, path)
144
+ })
145
+ ```
146
+
147
+ The callback receives:
148
+
149
+ - `key`: the current object key or array index
150
+ - `value`: the current value
151
+ - `path`: the full path to the current value
152
+
153
+ ## map
154
+
155
+ Walk through an object and build a new result.
156
+
157
+ ```ts
158
+ const result = map(data, (key, value, store, path) => {
159
+ if (typeof value === 'string') {
160
+ set(store, path, value.trim())
161
+ }
162
+ })
163
+ ```
164
+
165
+ The callback receives:
166
+
167
+ - `key`: the current object key or array index
168
+ - `value`: the current value
169
+ - `store`: the result object being built
170
+ - `path`: the full path to the current value
171
+
172
+ `map` returns `store`.
173
+
174
+ ## Path syntax
175
+
176
+ | Syntax | Description |
177
+ | --- | --- |
178
+ | `user.name` | Read or write a nested property. |
179
+ | `items[0].title` | Read or write an array item by index. |
180
+ | `user.{name,email}` | Pick several keys from one object. |
181
+ | `user.{*}` | List keys from one object. |
182
+ | `items[n].title` | Map every item in an array during `extract`. |
183
+
184
+ ## API
185
+
186
+ ```ts
187
+ pick(data, path)
188
+ set(data, path, value)
189
+ extract(data, mapping)
190
+ each(data, callback)
191
+ map(data, callback)
192
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ each: () => each,
24
+ extract: () => extract,
25
+ map: () => map,
26
+ pick: () => pick,
27
+ set: () => set
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/each.ts
32
+ var isWalkable = (value) => {
33
+ return value !== null && typeof value === "object";
34
+ };
35
+ var createPath = (parentPath, key, parentIsArray) => {
36
+ if (parentIsArray) {
37
+ return parentPath + "[" + key + "]";
38
+ }
39
+ return parentPath ? parentPath + "." + key : String(key);
40
+ };
41
+ var each = (data, callback) => {
42
+ const walk = (value, path, key, parentIsArray) => {
43
+ const currentPath = path || createPath("", key, parentIsArray);
44
+ callback(key, value, currentPath);
45
+ if (!isWalkable(value)) {
46
+ return;
47
+ }
48
+ if (Array.isArray(value)) {
49
+ value.forEach((item, index) => {
50
+ walk(item, createPath(currentPath, index, true), index, true);
51
+ });
52
+ return;
53
+ }
54
+ Object.entries(value).forEach(([childKey, item]) => {
55
+ walk(item, createPath(currentPath, childKey, false), childKey, false);
56
+ });
57
+ };
58
+ if (!isWalkable(data)) {
59
+ return;
60
+ }
61
+ if (Array.isArray(data)) {
62
+ data.forEach((item, index) => {
63
+ walk(item, createPath("", index, true), index, true);
64
+ });
65
+ return;
66
+ }
67
+ Object.entries(data).forEach(([key, value]) => {
68
+ walk(value, createPath("", key, false), key, false);
69
+ });
70
+ };
71
+
72
+ // src/helpers/getValueByPath.ts
73
+ var getValueByPath = (obj, path) => {
74
+ if (!path) return obj;
75
+ return path.split(".").reduce((result, current) => {
76
+ if (result !== null && result !== void 0) {
77
+ const match = current.match(/(w+)[(d+)]/);
78
+ if (match) {
79
+ const [, key, index] = match;
80
+ const array = result[key];
81
+ return array && Array.isArray(array) ? array[parseInt(index, 10)] : void 0;
82
+ }
83
+ return result[current];
84
+ }
85
+ return void 0;
86
+ }, obj);
87
+ };
88
+
89
+ // src/helpers/parsePathSelection.ts
90
+ var selectionRegex = /(.*){([^}]+)}$/;
91
+ var normalizeSelectionPath = (path) => {
92
+ return path.endsWith(".") ? path.slice(0, -1) : path;
93
+ };
94
+ var parseSelectedKeys = (selection) => {
95
+ if (selection.trim() === "*") {
96
+ return "all";
97
+ }
98
+ return selection.split(",").map((key) => key.trim()).filter(Boolean);
99
+ };
100
+ var parsePathSelection = (path = "") => {
101
+ const matches = path.match(selectionRegex);
102
+ if (!matches) {
103
+ return {
104
+ cleanPath: path,
105
+ keys: []
106
+ };
107
+ }
108
+ return {
109
+ cleanPath: normalizeSelectionPath(matches[1]),
110
+ keys: parseSelectedKeys(matches[2])
111
+ };
112
+ };
113
+
114
+ // src/pick.ts
115
+ var pick = (data = {}, path = "") => {
116
+ const { cleanPath, keys } = parsePathSelection(path);
117
+ const value = getValueByPath(data, cleanPath);
118
+ if (keys.length === 0 && keys !== "all") {
119
+ return value;
120
+ }
121
+ if (keys === "all") {
122
+ if (value && typeof value === "object" && !Array.isArray(value)) {
123
+ return Object.keys(value);
124
+ }
125
+ return [];
126
+ }
127
+ if (value && typeof value === "object") {
128
+ if (keys.length === 1) {
129
+ return value[keys[0]];
130
+ }
131
+ return keys.reduce(
132
+ (result, key) => ({
133
+ ...result,
134
+ [key]: value[key]
135
+ }),
136
+ {}
137
+ );
138
+ }
139
+ return void 0;
140
+ };
141
+
142
+ // src/set.ts
143
+ var parseArrayPathSegment = (key) => {
144
+ const matches = key.match(/^(\w+)\[(\d+)\]$/);
145
+ if (matches) {
146
+ return {
147
+ key: matches[1],
148
+ isArray: true,
149
+ index: parseInt(matches[2], 10)
150
+ };
151
+ }
152
+ return { key, isArray: false, index: -1 };
153
+ };
154
+ var set = (data = {}, path, value) => {
155
+ const keys = path.split(".");
156
+ let current = data;
157
+ for (let i = 0; i < keys.length; i++) {
158
+ const { key, isArray, index } = parseArrayPathSegment(keys[i]);
159
+ const isLastKey = i === keys.length - 1;
160
+ if (isArray) {
161
+ if (!current[key] || !Array.isArray(current[key])) {
162
+ current[key] = [];
163
+ }
164
+ if (index >= current[key].length) {
165
+ current[key].length = index + 1;
166
+ }
167
+ if (!isLastKey && current[key][index] === void 0) {
168
+ current[key][index] = {};
169
+ }
170
+ if (isLastKey) {
171
+ current[key][index] = value;
172
+ break;
173
+ }
174
+ current = current[key][index];
175
+ } else if (!isLastKey) {
176
+ if (!current[key] || typeof current[key] !== "object") {
177
+ current[key] = {};
178
+ }
179
+ current = current[key];
180
+ } else {
181
+ current[key] = value;
182
+ }
183
+ }
184
+ return data;
185
+ };
186
+
187
+ // src/extract.ts
188
+ var mapArrayItems = (destinationPath, sourcePath, source = {}, result = {}) => {
189
+ const destinationParts = destinationPath.split("[n].");
190
+ const sourceParts = sourcePath.split("[n].");
191
+ const array = pick(source, sourceParts[0]);
192
+ if (Array.isArray(array)) {
193
+ array.forEach((row, index) => {
194
+ const itemPath = destinationParts[0] + "[" + index + "]." + destinationParts[1];
195
+ set(result, itemPath, pick(row, sourceParts[1]));
196
+ });
197
+ }
198
+ };
199
+ var extract = (source, mapping) => {
200
+ const result = {};
201
+ Object.entries(mapping).forEach(([destinationPath, sourcePath]) => {
202
+ if (destinationPath.includes("[n]") && typeof sourcePath === "string") {
203
+ mapArrayItems(destinationPath, sourcePath, source, result);
204
+ return;
205
+ }
206
+ if (Array.isArray(sourcePath)) {
207
+ for (const item of sourcePath) {
208
+ const value = pick(source, item);
209
+ if (value !== void 0) {
210
+ set(result, destinationPath, value);
211
+ break;
212
+ }
213
+ }
214
+ return;
215
+ }
216
+ set(result, destinationPath, pick(source, sourcePath));
217
+ });
218
+ return result;
219
+ };
220
+
221
+ // src/map.ts
222
+ var map = (data, callback, store = {}) => {
223
+ each(data, (key, value, path) => {
224
+ callback(key, value, store, path);
225
+ });
226
+ return store;
227
+ };
228
+ // Annotate the CommonJS export names for ESM import in node:
229
+ 0 && (module.exports = {
230
+ each,
231
+ extract,
232
+ map,
233
+ pick,
234
+ set
235
+ });
@@ -0,0 +1,17 @@
1
+ type WalkKey = string | number;
2
+ type EachCallback = (key: WalkKey, value: any, path: string) => void;
3
+ declare const each: (data: any, callback: EachCallback) => void;
4
+
5
+ type ObjwalkObject$1 = Record<string, any>;
6
+ declare const pick: (data?: ObjwalkObject$1, path?: string) => any;
7
+
8
+ type ExtractMapping = Record<string, string | string[]>;
9
+ declare const extract: (source: ObjwalkObject$1, mapping: ExtractMapping) => ObjwalkObject$1;
10
+
11
+ type MapCallback<TStore extends Record<string, any> = Record<string, any>> = (key: WalkKey, value: any, store: TStore, path: string) => void;
12
+ declare const map: <TStore extends Record<string, any> = Record<string, any>>(data: any, callback: MapCallback<TStore>, store?: TStore) => TStore;
13
+
14
+ type ObjwalkObject = Record<string, any>;
15
+ declare const set: (data: ObjwalkObject | undefined, path: string, value: any) => ObjwalkObject;
16
+
17
+ export { type EachCallback, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type WalkKey, each, extract, map, pick, set };
@@ -0,0 +1,17 @@
1
+ type WalkKey = string | number;
2
+ type EachCallback = (key: WalkKey, value: any, path: string) => void;
3
+ declare const each: (data: any, callback: EachCallback) => void;
4
+
5
+ type ObjwalkObject$1 = Record<string, any>;
6
+ declare const pick: (data?: ObjwalkObject$1, path?: string) => any;
7
+
8
+ type ExtractMapping = Record<string, string | string[]>;
9
+ declare const extract: (source: ObjwalkObject$1, mapping: ExtractMapping) => ObjwalkObject$1;
10
+
11
+ type MapCallback<TStore extends Record<string, any> = Record<string, any>> = (key: WalkKey, value: any, store: TStore, path: string) => void;
12
+ declare const map: <TStore extends Record<string, any> = Record<string, any>>(data: any, callback: MapCallback<TStore>, store?: TStore) => TStore;
13
+
14
+ type ObjwalkObject = Record<string, any>;
15
+ declare const set: (data: ObjwalkObject | undefined, path: string, value: any) => ObjwalkObject;
16
+
17
+ export { type EachCallback, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type WalkKey, each, extract, map, pick, set };
package/dist/index.js ADDED
@@ -0,0 +1,204 @@
1
+ // src/each.ts
2
+ var isWalkable = (value) => {
3
+ return value !== null && typeof value === "object";
4
+ };
5
+ var createPath = (parentPath, key, parentIsArray) => {
6
+ if (parentIsArray) {
7
+ return parentPath + "[" + key + "]";
8
+ }
9
+ return parentPath ? parentPath + "." + key : String(key);
10
+ };
11
+ var each = (data, callback) => {
12
+ const walk = (value, path, key, parentIsArray) => {
13
+ const currentPath = path || createPath("", key, parentIsArray);
14
+ callback(key, value, currentPath);
15
+ if (!isWalkable(value)) {
16
+ return;
17
+ }
18
+ if (Array.isArray(value)) {
19
+ value.forEach((item, index) => {
20
+ walk(item, createPath(currentPath, index, true), index, true);
21
+ });
22
+ return;
23
+ }
24
+ Object.entries(value).forEach(([childKey, item]) => {
25
+ walk(item, createPath(currentPath, childKey, false), childKey, false);
26
+ });
27
+ };
28
+ if (!isWalkable(data)) {
29
+ return;
30
+ }
31
+ if (Array.isArray(data)) {
32
+ data.forEach((item, index) => {
33
+ walk(item, createPath("", index, true), index, true);
34
+ });
35
+ return;
36
+ }
37
+ Object.entries(data).forEach(([key, value]) => {
38
+ walk(value, createPath("", key, false), key, false);
39
+ });
40
+ };
41
+
42
+ // src/helpers/getValueByPath.ts
43
+ var getValueByPath = (obj, path) => {
44
+ if (!path) return obj;
45
+ return path.split(".").reduce((result, current) => {
46
+ if (result !== null && result !== void 0) {
47
+ const match = current.match(/(w+)[(d+)]/);
48
+ if (match) {
49
+ const [, key, index] = match;
50
+ const array = result[key];
51
+ return array && Array.isArray(array) ? array[parseInt(index, 10)] : void 0;
52
+ }
53
+ return result[current];
54
+ }
55
+ return void 0;
56
+ }, obj);
57
+ };
58
+
59
+ // src/helpers/parsePathSelection.ts
60
+ var selectionRegex = /(.*){([^}]+)}$/;
61
+ var normalizeSelectionPath = (path) => {
62
+ return path.endsWith(".") ? path.slice(0, -1) : path;
63
+ };
64
+ var parseSelectedKeys = (selection) => {
65
+ if (selection.trim() === "*") {
66
+ return "all";
67
+ }
68
+ return selection.split(",").map((key) => key.trim()).filter(Boolean);
69
+ };
70
+ var parsePathSelection = (path = "") => {
71
+ const matches = path.match(selectionRegex);
72
+ if (!matches) {
73
+ return {
74
+ cleanPath: path,
75
+ keys: []
76
+ };
77
+ }
78
+ return {
79
+ cleanPath: normalizeSelectionPath(matches[1]),
80
+ keys: parseSelectedKeys(matches[2])
81
+ };
82
+ };
83
+
84
+ // src/pick.ts
85
+ var pick = (data = {}, path = "") => {
86
+ const { cleanPath, keys } = parsePathSelection(path);
87
+ const value = getValueByPath(data, cleanPath);
88
+ if (keys.length === 0 && keys !== "all") {
89
+ return value;
90
+ }
91
+ if (keys === "all") {
92
+ if (value && typeof value === "object" && !Array.isArray(value)) {
93
+ return Object.keys(value);
94
+ }
95
+ return [];
96
+ }
97
+ if (value && typeof value === "object") {
98
+ if (keys.length === 1) {
99
+ return value[keys[0]];
100
+ }
101
+ return keys.reduce(
102
+ (result, key) => ({
103
+ ...result,
104
+ [key]: value[key]
105
+ }),
106
+ {}
107
+ );
108
+ }
109
+ return void 0;
110
+ };
111
+
112
+ // src/set.ts
113
+ var parseArrayPathSegment = (key) => {
114
+ const matches = key.match(/^(\w+)\[(\d+)\]$/);
115
+ if (matches) {
116
+ return {
117
+ key: matches[1],
118
+ isArray: true,
119
+ index: parseInt(matches[2], 10)
120
+ };
121
+ }
122
+ return { key, isArray: false, index: -1 };
123
+ };
124
+ var set = (data = {}, path, value) => {
125
+ const keys = path.split(".");
126
+ let current = data;
127
+ for (let i = 0; i < keys.length; i++) {
128
+ const { key, isArray, index } = parseArrayPathSegment(keys[i]);
129
+ const isLastKey = i === keys.length - 1;
130
+ if (isArray) {
131
+ if (!current[key] || !Array.isArray(current[key])) {
132
+ current[key] = [];
133
+ }
134
+ if (index >= current[key].length) {
135
+ current[key].length = index + 1;
136
+ }
137
+ if (!isLastKey && current[key][index] === void 0) {
138
+ current[key][index] = {};
139
+ }
140
+ if (isLastKey) {
141
+ current[key][index] = value;
142
+ break;
143
+ }
144
+ current = current[key][index];
145
+ } else if (!isLastKey) {
146
+ if (!current[key] || typeof current[key] !== "object") {
147
+ current[key] = {};
148
+ }
149
+ current = current[key];
150
+ } else {
151
+ current[key] = value;
152
+ }
153
+ }
154
+ return data;
155
+ };
156
+
157
+ // src/extract.ts
158
+ var mapArrayItems = (destinationPath, sourcePath, source = {}, result = {}) => {
159
+ const destinationParts = destinationPath.split("[n].");
160
+ const sourceParts = sourcePath.split("[n].");
161
+ const array = pick(source, sourceParts[0]);
162
+ if (Array.isArray(array)) {
163
+ array.forEach((row, index) => {
164
+ const itemPath = destinationParts[0] + "[" + index + "]." + destinationParts[1];
165
+ set(result, itemPath, pick(row, sourceParts[1]));
166
+ });
167
+ }
168
+ };
169
+ var extract = (source, mapping) => {
170
+ const result = {};
171
+ Object.entries(mapping).forEach(([destinationPath, sourcePath]) => {
172
+ if (destinationPath.includes("[n]") && typeof sourcePath === "string") {
173
+ mapArrayItems(destinationPath, sourcePath, source, result);
174
+ return;
175
+ }
176
+ if (Array.isArray(sourcePath)) {
177
+ for (const item of sourcePath) {
178
+ const value = pick(source, item);
179
+ if (value !== void 0) {
180
+ set(result, destinationPath, value);
181
+ break;
182
+ }
183
+ }
184
+ return;
185
+ }
186
+ set(result, destinationPath, pick(source, sourcePath));
187
+ });
188
+ return result;
189
+ };
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
+ export {
199
+ each,
200
+ extract,
201
+ map,
202
+ pick,
203
+ set
204
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "objwalk",
3
+ "version": "0.1.0",
4
+ "description": "Small utilities for reading, writing, walking, and reshaping nested JavaScript objects.",
5
+ "keywords": [
6
+ "object",
7
+ "path",
8
+ "object-path",
9
+ "pick",
10
+ "set",
11
+ "extract",
12
+ "map",
13
+ "walk",
14
+ "traverse",
15
+ "transform",
16
+ "nested",
17
+ "mapper"
18
+ ],
19
+ "license": "ISC",
20
+ "author": "Sergey Khalilov <sergey.khalilov@gmail.com>",
21
+ "type": "module",
22
+ "main": "./dist/index.cjs",
23
+ "module": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js",
29
+ "require": "./dist/index.cjs"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
38
+ "test": "echo \"Error: no test specified\" && exit 1",
39
+ "pack:check": "npm pack --dry-run"
40
+ },
41
+ "devDependencies": {
42
+ "tsup": "^8.5.1",
43
+ "typescript": "^5.9.3"
44
+ }
45
+ }