@karmaniverous/jsonmap 2.0.6 → 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 +227 -53
- package/dist/index.cjs +22052 -17299
- package/dist/index.d.cts +83 -41
- package/dist/index.d.mts +83 -41
- package/dist/index.d.ts +83 -41
- package/dist/index.mjs +22049 -17300
- package/jsonmap.schema.json +108 -0
- package/package.json +40 -46
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# JsonMap
|
|
2
2
|
|
|
3
|
-
`JsonMap` is a JSON mapping library
|
|
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
|
-
|
|
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
|
-
|
|
19
|
+
Because the `map` is a POJO:
|
|
16
20
|
|
|
17
|
-
|
|
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
|
-
|
|
51
|
+
## Map Structure
|
|
25
52
|
|
|
26
|
-
|
|
53
|
+
The transformation output mirrors the structure of your `map` object. Values in the map can be:
|
|
27
54
|
|
|
28
|
-
|
|
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
|
-
|
|
84
|
+
### Path Syntax
|
|
31
85
|
|
|
32
|
-
|
|
86
|
+
All `method` and `params` values use lodash-style dot paths with special root prefixes:
|
|
33
87
|
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
+
### Private Keys (`$`-prefixed)
|
|
39
114
|
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
201
|
+
## JSON Schema & Zod Schemas
|
|
94
202
|
|
|
95
|
-
|
|
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
|
-
|
|
205
|
+
### IDE autocomplete for config files
|
|
98
206
|
|
|
99
|
-
|
|
207
|
+
Point your JSON map config file at the published schema:
|
|
100
208
|
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
import { JsonMap } from '@karmaniverous/jsonmap';
|
|
222
|
+
### Referencing the schema from other JSON Schema files
|
|
105
223
|
|
|
106
|
-
|
|
107
|
-
const jsonMap = new JsonMap(map, lib);
|
|
224
|
+
Use `$ref` to compose the JsonMap schema into your own:
|
|
108
225
|
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
|