@karmaniverous/jsonmap 2.0.5 → 2.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 CHANGED
@@ -1,8 +1,6 @@
1
1
  # JsonMap
2
2
 
3
- `JsonMap` is a JSON mapping library, which facilitates the transformation of some input JSON object according to a set of rules.
4
-
5
- Installing `JsonMap` is easy:
3
+ `JsonMap` is a JSON mapping library that facilitates the transformation of an input JSON object according to a set of declarative rules.
6
4
 
7
5
  ```bash
8
6
  npm install @karmaniverous/jsonmap
@@ -10,107 +8,283 @@ npm install @karmaniverous/jsonmap
10
8
 
11
9
  `JsonMap` is _hyper-generic_: you bring your own mapping functions, which may be async and may be combined into complex transformation logic.
12
10
 
13
- To do this, create a `lib` object, which combines your mapping function libraries into a single object. You can use async functions and organize this in any way that makes sense.
11
+ ## Why?
12
+
13
+ Mapping data from one form into another is a critical requirement of virtually every application.
14
+
15
+ `JsonMap` decouples mapping structure from mapping logic — and drives that decoupling deep into the logic layer.
16
+
17
+ The `lib` object contains your mapping functions, organized however you like. The `map` object is a plain JSON object ([POJO](https://masteringjs.io/tutorials/fundamentals/pojo)) that expresses your mapping rules declaratively.
14
18
 
15
- For example:
19
+ Because the `map` is a POJO:
16
20
 
17
- ```js
21
+ - It can be stored in a database or config file.
22
+ - It does NOT express code as text, exposing a minimal threat surface.
23
+ - It **transforms application logic into structured configuration data**, enabling more generic, flexible applications.
24
+
25
+ ## Quick Start
26
+
27
+ ```ts
18
28
  import _ from 'lodash';
19
29
  import numeral from 'numeral';
30
+ import { JsonMap } from '@karmaniverous/jsonmap';
20
31
 
32
+ // 1. Create a lib object with your mapping functions.
21
33
  const lib = { _, numeral };
34
+
35
+ // 2. Define a map — a POJO expressing your transformation rules.
36
+ const map = {
37
+ name: {
38
+ $: { method: '$.lib._.get', params: ['$.input', 'user.name'] },
39
+ },
40
+ greeting: {
41
+ $: { method: '$.lib._.toUpper', params: '$.output.name' },
42
+ },
43
+ };
44
+
45
+ // 3. Create a JsonMap instance and transform your input.
46
+ const jsonMap = new JsonMap(map, lib);
47
+ const output = await jsonMap.transform({ user: { name: 'Alice' } });
48
+ // → { name: 'Alice', greeting: 'ALICE' }
22
49
  ```
23
50
 
24
- You also need to create a `map` object. This is a [plain old Javascript object](https://masteringjs.io/tutorials/fundamentals/pojo) (POJO) that expresses your mapping rules.
51
+ ## Map Structure
25
52
 
26
- ## Why?
53
+ The transformation output mirrors the structure of your `map` object. Values in the map can be:
27
54
 
28
- Mapping data from one form into another is a critical requirement of virtually every application.
55
+ - **Static values** passed through to output unchanged.
56
+ - **Dynamic nodes** — objects with a single `$` key, containing one or more transformation steps.
57
+ - **Nested objects/arrays** — recursively processed.
58
+
59
+ ### Dynamic Nodes
60
+
61
+ A dynamic node is an object with a single `$` key. Its value is either a single transform or an array of transforms executed in sequence:
62
+
63
+ ```ts
64
+ // Single transform
65
+ { $: { method: '$.lib._.get', params: ['$.input', 'some.path'] } }
66
+
67
+ // Transform pipeline — output of each step feeds into the next
68
+ {
69
+ $: [
70
+ { method: '$.lib._.get', params: ['$.input', 'value'] },
71
+ { method: '$.lib.numeral', params: '$[0]' },
72
+ { method: '$[0].format', params: '$0,0.00' },
73
+ ],
74
+ }
75
+ ```
76
+
77
+ Each transform step has:
78
+
79
+ | Property | Type | Description |
80
+ | --- | --- | --- |
81
+ | `method` | `string` | Path to the function to call (see [Path Syntax](#path-syntax)) |
82
+ | `params` | `string \| string[]` | One or more paths resolved as arguments to the method |
29
83
 
30
- `JsonMap` decouples mapping structure from mapping logic... and drives that decoupling deep into the logic layer.
84
+ ### Path Syntax
31
85
 
32
- The `lib` object contains the remaining logic that CAN'T be decoupled, and can be used consistently across your application.
86
+ All `method` and `params` values use lodash-style dot paths with special root prefixes:
33
87
 
34
- The `map` object is a [POJO](https://masteringjs.io/tutorials/fundamentals/pojo), which can easily be stored in a database yet does NOT express code as text and thus exposes a minimal threat surface.
88
+ | Prefix | Resolves to |
89
+ | --- | --- |
90
+ | `$.lib.*` | Your `lib` object (e.g. `$.lib._.get`) |
91
+ | `$.input.*` | The original input data |
92
+ | `$.output.*` | The output built so far (enables progressive transforms) |
93
+ | `$[i].*` | Result of the _i_-th previous transform step in the current pipeline (0 = most recent) |
35
94
 
36
- **This allows you to transform application logic into structured configuration data and write more generic, flexible applications.**
95
+ Paths without a `$` prefix are treated as literal strings.
96
+
97
+ ### Progressive Transformations
98
+
99
+ Because transforms are processed in key order and `$.output.*` references the output built so far, later keys can reference earlier ones:
100
+
101
+ ```ts
102
+ const map = {
103
+ firstName: {
104
+ $: { method: '$.lib._.get', params: ['$.input', 'first'] },
105
+ },
106
+ // This runs AFTER firstName because keys are sorted
107
+ fullGreeting: {
108
+ $: { method: '$.lib._.toUpper', params: '$.output.firstName' },
109
+ },
110
+ };
111
+ ```
37
112
 
38
- ## Usage
113
+ ### Private Keys (`$`-prefixed)
39
114
 
40
- The transformation output will reflect the structure of your `map` object and include any static values. To add mapping logic, use a structured value that consists of an object with a single `$` key, like this:
115
+ Keys starting with `$` are **stripped from the final output** but are available during transformation via `$.output.*`. This enables intermediate computations:
41
116
 
42
117
  ```ts
118
+ const map = {
119
+ // Private: used for an API call, then stripped from output
120
+ $apiParams: {
121
+ merchantId: {
122
+ $: { method: '$.lib._.get', params: ['$.input', 'merchant.id'] },
123
+ },
124
+ },
125
+ // Public: references the private key's output
126
+ merchantName: {
127
+ $: {
128
+ method: '$.lib.fetchMerchant',
129
+ params: '$.output.$apiParams.merchantId',
130
+ },
131
+ },
132
+ };
133
+ ```
134
+
135
+ ### Controlling Key Stripping with `ignore`
136
+
137
+ The `ignore` option (a `string` or `RegExp`) controls which keys are stripped. The default is `/^\$/` (all `$`-prefixed keys). You can override it to keep specific keys:
138
+
139
+ ```ts
140
+ // Keep $metadata in output, strip all other $-prefixed keys
141
+ const jsonMap = new JsonMap(map, lib, { ignore: '^\\$(?!metadata)' });
142
+ ```
143
+
144
+ ### Recursive Evaluation
145
+
146
+ If a dynamic node's output is itself a dynamic node (an object with a single `$` key), it will be re-evaluated recursively until a non-dynamic value is produced.
147
+
148
+ ## API
149
+
150
+ ### `new JsonMap(map, lib, options?)`
151
+
152
+ | Parameter | Type | Description |
153
+ | --- | --- | --- |
154
+ | `map` | `JsonMapMap` | The map definition (POJO) |
155
+ | `lib` | `JsonMapLib` | Object containing your mapping functions |
156
+ | `options` | `JsonMapOptions` | Optional. `{ ignore?: string \| RegExp }` — pattern for keys to strip from output (default: `/^\$/`) |
157
+
158
+ ### `jsonMap.transform(input): Promise<Json>`
159
+
160
+ Transforms the input data according to the map. The transformation is asynchronous — your lib functions may be async.
161
+
162
+ ## Full Example
163
+
164
+ ```ts
165
+ import _ from 'lodash';
166
+ import numeral from 'numeral';
167
+ import { JsonMap } from '@karmaniverous/jsonmap';
168
+
169
+ const lib = { _, numeral };
170
+
43
171
  const map = {
44
172
  foo: 'static value passed directly to output',
45
- // Structure passed directly to output.
46
173
  bar: [
47
174
  {
48
175
  static: 'another static value',
49
- // Keys starting with $ are available for progressive transformations but
50
- // are not passed to the output object.
51
- $remove: 'this should be removed from the output',
52
- // Value defined by a mapping rule expressing an array of transformation
53
- // objects. If there is only a single transformation object, no array is
54
- // necessary. The output of the last transformation step is returned as
55
- // the mapped value.
176
+ $remove: 'stripped from output (private key)',
56
177
  dynamic: {
57
178
  $: [
58
- // Each transformation object uses a special syntax to reference an
59
- // a method to run and an array of parameters to pass to it.
60
179
  {
61
180
  method: '$.lib._.get',
62
181
  params: ['$.input', 'dynamodb.NewImage.roundup.N'],
63
182
  },
64
- // The special syntax uses lodash-style paths. Its root object can
65
- // reference the lib object ($.lib...), the transformation input
66
- // ($.input...), the output generated so far ($.output...), or the
67
- // outputs of previous transformation steps ($.[0]..., $.[1]...).
68
- {
69
- method: '$.lib.numeral',
70
- // If there is only a single param, no array is necessary.
71
- params: '$[0]',
72
- },
73
- {
74
- method: '$[0].format',
75
- params: '$0,0.00',
76
- },
183
+ { method: '$.lib.numeral', params: '$[0]' },
184
+ { method: '$[0].format', params: '$0,0.00' },
77
185
  ],
78
186
  },
79
187
  },
80
188
  ],
81
- // Value defined by a single mapping rule executing a method against a
82
- // previous output of the same mapping object.
83
189
  progressive: {
84
190
  $: {
85
191
  method: '$.lib._.toUpper',
86
192
  params: '$.output.bar[0].static',
87
193
  },
88
194
  },
89
- $remove: 'this should be removed from the output',
90
195
  };
196
+
197
+ const jsonMap = new JsonMap(map, lib);
198
+ const output = await jsonMap.transform(someInput);
91
199
  ```
92
200
 
93
- The transformation process is _generic_ and _asynchronous_. Feel free to use any function from any source in your `lib` object.
201
+ ## JSON Schema & Zod Schemas
94
202
 
95
- Your `lib` object can also include locally defined or anonymous functions. Combined with the `$[i]...` syntax, this allows for complex branching transformation logic.
203
+ This package exports [Zod](https://zod.dev/) schemas as the source of truth for all map-related types, plus a generated [JSON Schema](https://json-schema.org/) file for editor tooling and cross-language validation.
96
204
 
97
- Mapping objects are _recursive_. If a mapping object (i.e. `{ $: ... }`) renders another mapping object, it will be processed recursively until it does not.
205
+ ### IDE autocomplete for config files
98
206
 
99
- Input objects can contain data of any kind, including functions. These can be executed as methods of their parent objects using transformation steps.
207
+ Point your JSON map config file at the published schema:
100
208
 
101
- Once a `JsonMap` instance is configured, it can be executed against any input. Configure & execute a `JsonMap` instance like this:
209
+ ```json
210
+ {
211
+ "$schema": "node_modules/@karmaniverous/jsonmap/jsonmap.schema.json",
212
+ "foo": "static value",
213
+ "bar": {
214
+ "$": {
215
+ "method": "$.lib._.get",
216
+ "params": ["$.input", "some.path"]
217
+ }
218
+ }
219
+ }
220
+ ```
102
221
 
103
- ```js
104
- import { JsonMap } from '@karmaniverous/jsonmap';
222
+ ### Referencing the schema from other JSON Schema files
105
223
 
106
- // Assumes map & lib are already defined as above.
107
- const jsonMap = new JsonMap(map, lib);
224
+ Use `$ref` to compose the JsonMap schema into your own:
108
225
 
109
- // Assumes some input data object is already defined.
110
- const output = await jsonMap.transform(input);
226
+ ```json
227
+ {
228
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
229
+ "type": "object",
230
+ "properties": {
231
+ "mappings": {
232
+ "$ref": "node_modules/@karmaniverous/jsonmap/jsonmap.schema.json"
233
+ }
234
+ }
235
+ }
111
236
  ```
112
237
 
113
- The [unit tests](https://github.com/karmaniverous/jsonmap/blob/main/lib/JsonMap/JsonMap.test.js) demonstrate this example in action.
238
+ ### Composing the Zod schemas in TypeScript
239
+
240
+ Import the exported Zod schemas to build on top of them:
241
+
242
+ ```ts
243
+ import { z } from 'zod';
244
+ import {
245
+ jsonMapMapSchema,
246
+ jsonMapTransformSchema,
247
+ jsonMapDynamicSchema,
248
+ jsonMapOptionsSchema,
249
+ } from '@karmaniverous/jsonmap';
250
+
251
+ // Extend with your own config shape
252
+ const myConfigSchema = z.object({
253
+ name: z.string(),
254
+ map: jsonMapMapSchema,
255
+ options: jsonMapOptionsSchema.optional(),
256
+ });
257
+
258
+ type MyConfig = z.infer<typeof myConfigSchema>;
259
+
260
+ // Validate at runtime
261
+ const config = myConfigSchema.parse(untrustedInput);
262
+ ```
263
+
264
+ ### Exported Schemas
265
+
266
+ | Schema | Describes |
267
+ | --- | --- |
268
+ | `jsonMapTransformSchema` | A single `{ method, params }` transform step |
269
+ | `jsonMapDynamicSchema` | A `{ $: ... }` dynamic value node |
270
+ | `jsonMapMapSchema` | A full recursive map definition (literals, objects, arrays) |
271
+ | `jsonMapOptionsSchema` | Constructor options (`{ ignore?: string \| RegExp }`) |
272
+
273
+ ### Exported Types
274
+
275
+ All types are derived from their Zod schemas via `z.infer<>`:
276
+
277
+ | Type | Description |
278
+ | --- | --- |
279
+ | `JsonMapTransform` | A single transform step |
280
+ | `JsonMapDynamic` | A dynamic value node |
281
+ | `JsonMapMap` | A recursive map definition |
282
+ | `JsonMapOptions` | Constructor options |
283
+ | `JsonMapLib` | Library of mapping functions |
284
+ | `Json` | Any valid JSON value |
285
+ | `JsonFn` | JSON replacer/reviver function |
286
+ | `PathResolutionMap` | Map of path patterns to resolver functions |
287
+ | `PathResolutionParams` | Parameters for path resolution |
114
288
 
115
289
  ---
116
290