@karmaniverous/jsonmap 0.0.2
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/.env +1 -0
- package/README.md +142 -0
- package/dist/default/lib/JsonMap/JsonMap.js +162 -0
- package/dist/default/lib/index.js +12 -0
- package/dist/package.json +3 -0
- package/env/.env.dev +2 -0
- package/env/dynamic.js +1 -0
- package/lib/JsonMap/JsonMap.js +162 -0
- package/lib/index.js +1 -0
- package/package.json +84 -0
- package/prompt.md +227 -0
package/.env
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Load with getdotenv: https://github.com/karmaniverous/get-dotenv
|
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# JsonMap
|
|
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:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @karmaniverous/jsonmap
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`JsonMap` is _hyper-generic_: you bring your own mapping functions, which may be async and may be combined into complex transformation logic.
|
|
12
|
+
|
|
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.
|
|
14
|
+
|
|
15
|
+
For example:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import _ from 'lodash';
|
|
19
|
+
import numeral from 'numeral';
|
|
20
|
+
|
|
21
|
+
const lib = { _, numeral };
|
|
22
|
+
```
|
|
23
|
+
|
|
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.
|
|
25
|
+
|
|
26
|
+
## Why?
|
|
27
|
+
|
|
28
|
+
Mapping data from one form into another is a critical requirement of virtually every application.
|
|
29
|
+
|
|
30
|
+
`JsonMap` decouples mapping structure from mapping logic... and drives that decoupling deep into the logic layer.
|
|
31
|
+
|
|
32
|
+
The `lib` object contains the remaining logic that CAN'T be decoupled, and can be used consistently across your application.
|
|
33
|
+
|
|
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.
|
|
35
|
+
|
|
36
|
+
**This allows you to transform application logic into structured configuration data and write more generic, flexible applications.**
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
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:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
const map = {
|
|
44
|
+
key1: 'static value passed directly to output',
|
|
45
|
+
// Structure passed directly to output.
|
|
46
|
+
key2: [
|
|
47
|
+
{
|
|
48
|
+
key2a: 'another static value',
|
|
49
|
+
// Value defined by mapping rule with an array of transformation objects.
|
|
50
|
+
// If there is only a single transformation object, no array is necessary.
|
|
51
|
+
key2b: {
|
|
52
|
+
$: [
|
|
53
|
+
// Each transformation object uses a special syntax to reference an
|
|
54
|
+
// object, a method to run on it, and an array of parameters to pass.
|
|
55
|
+
{
|
|
56
|
+
object: '$.lib._',
|
|
57
|
+
method: 'get',
|
|
58
|
+
params: ['$.input', 'dynamodb.NewImage.roundup.N'],
|
|
59
|
+
},
|
|
60
|
+
// The special syntax uses lodash-style paths. Its root object can
|
|
61
|
+
// reference the lib object ($.lib...), the transformation input
|
|
62
|
+
// ($.input...), the output generated so far ($.output...), or the
|
|
63
|
+
// outputs of previous transformation steps ($.[0]..., $.[1]...).
|
|
64
|
+
{
|
|
65
|
+
object: '$.lib',
|
|
66
|
+
method: 'numeral',
|
|
67
|
+
// If there is only a single param, no array is necessary.
|
|
68
|
+
params: '$[0]',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
object: '$[0]',
|
|
72
|
+
method: 'format',
|
|
73
|
+
params: '$0,0.00',
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The transformation process is _generic_ and _asynchronous_. Feel free to use any function from any source in your `lib` object.
|
|
83
|
+
|
|
84
|
+
Your `lib` object can also include locally defined or anonymous functions. Combined with the `$[i]...` syntax, this allows for complex branching transformation logic.
|
|
85
|
+
|
|
86
|
+
Mapping objects are _recursive_. If a mapping object (i.e. `{ $: ... }`) renders another mapping object, it will be processed recursively until it does not.
|
|
87
|
+
|
|
88
|
+
Input objects can contain data of any kind, including functions. These can be executed as methods of their parent objects using transformation steps.
|
|
89
|
+
|
|
90
|
+
Once a `JsonMap` instance is configured, it can be executed against any input. Configure & execute a `JsonMap` instance like this:
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
import { JsonMap } from '@karmaniverous/jsonmap';
|
|
94
|
+
|
|
95
|
+
// Assumes lib & map are already defined as above.
|
|
96
|
+
const jsonMap = new JsonMap(lib, map);
|
|
97
|
+
|
|
98
|
+
// Assumes some input data object is already defined.
|
|
99
|
+
const output = await jsonMap.transform(input);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
# API Documentation
|
|
103
|
+
|
|
104
|
+
<a name="JsonMap"></a>
|
|
105
|
+
|
|
106
|
+
## JsonMap
|
|
107
|
+
JsonMap class to apply transformations to a JSON object
|
|
108
|
+
|
|
109
|
+
**Kind**: global class
|
|
110
|
+
|
|
111
|
+
* [JsonMap](#JsonMap)
|
|
112
|
+
* [new JsonMap(lib, map)](#new_JsonMap_new)
|
|
113
|
+
* [.transform(input)](#JsonMap+transform) ⇒ <code>object</code>
|
|
114
|
+
|
|
115
|
+
<a name="new_JsonMap_new"></a>
|
|
116
|
+
|
|
117
|
+
### new JsonMap(lib, map)
|
|
118
|
+
Creates an instance of JsonMap.
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
| Param | Type | Description |
|
|
122
|
+
| --- | --- | --- |
|
|
123
|
+
| lib | <code>object</code> | A collection of function libraries. |
|
|
124
|
+
| map | <code>object</code> | The data mapping configuration. |
|
|
125
|
+
|
|
126
|
+
<a name="JsonMap+transform"></a>
|
|
127
|
+
|
|
128
|
+
### jsonMap.transform(input) ⇒ <code>object</code>
|
|
129
|
+
Transforms the input data according to the map configuration.
|
|
130
|
+
|
|
131
|
+
**Kind**: instance method of [<code>JsonMap</code>](#JsonMap)
|
|
132
|
+
**Returns**: <code>object</code> - - The transformed data.
|
|
133
|
+
|
|
134
|
+
| Param | Type | Description |
|
|
135
|
+
| --- | --- | --- |
|
|
136
|
+
| input | <code>object</code> | The input data to be transformed. |
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
See more great templates and other tools on
|
|
142
|
+
[my GitHub Profile](https://github.com/karmaniverous)!
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.JsonMap = void 0;
|
|
7
|
+
var _isNil2 = _interopRequireDefault(require("lodash/isNil"));
|
|
8
|
+
var _parseInt2 = _interopRequireDefault(require("lodash/parseInt"));
|
|
9
|
+
var _get2 = _interopRequireDefault(require("lodash/get"));
|
|
10
|
+
var _isString2 = _interopRequireDefault(require("lodash/isString"));
|
|
11
|
+
var _set2 = _interopRequireDefault(require("lodash/set"));
|
|
12
|
+
var _last2 = _interopRequireDefault(require("lodash/last"));
|
|
13
|
+
var _castArray2 = _interopRequireDefault(require("lodash/castArray"));
|
|
14
|
+
var _isArray2 = _interopRequireDefault(require("lodash/isArray"));
|
|
15
|
+
var _isObject2 = _interopRequireDefault(require("lodash/isObject"));
|
|
16
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
17
|
+
function _classPrivateMethodInitSpec(obj, privateSet) { _checkPrivateRedeclaration(obj, privateSet); privateSet.add(obj); }
|
|
18
|
+
function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
|
|
19
|
+
function _classPrivateMethodGet(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; }
|
|
20
|
+
var _transform = /*#__PURE__*/new WeakSet();
|
|
21
|
+
var _resolvePath = /*#__PURE__*/new WeakSet();
|
|
22
|
+
/**
|
|
23
|
+
* JsonMap class to apply transformations to a JSON object
|
|
24
|
+
*/
|
|
25
|
+
class JsonMap {
|
|
26
|
+
/**
|
|
27
|
+
* Creates an instance of JsonMap.
|
|
28
|
+
*
|
|
29
|
+
* @param {object} lib - A collection of function libraries.
|
|
30
|
+
* @param {object} map - The data mapping configuration.
|
|
31
|
+
*/
|
|
32
|
+
constructor(lib, map) {
|
|
33
|
+
_classPrivateMethodInitSpec(this, _resolvePath);
|
|
34
|
+
_classPrivateMethodInitSpec(this, _transform);
|
|
35
|
+
this.lib = lib;
|
|
36
|
+
this.map = map;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Transforms the input data according to the map configuration.
|
|
41
|
+
*
|
|
42
|
+
* @param {object} input - The input data to be transformed.
|
|
43
|
+
* @returns {object} - The transformed data.
|
|
44
|
+
*/
|
|
45
|
+
async transform(input) {
|
|
46
|
+
// Sets the input data and initializes an empty output object
|
|
47
|
+
this.input = input;
|
|
48
|
+
this.output = {};
|
|
49
|
+
|
|
50
|
+
// Calls the #transform method to perform the transformation
|
|
51
|
+
return await _classPrivateMethodGet(this, _transform, _transform2).call(this, this.map, this.input, this.output);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Recursive function to handle transformations.
|
|
56
|
+
*
|
|
57
|
+
* @param {object} node - The current map node.
|
|
58
|
+
* @param {object} input - The current input node.
|
|
59
|
+
* @param {object} output - The current output node.
|
|
60
|
+
* @param {string} path - The path to the current node.
|
|
61
|
+
* @returns {object} - The transformed node.
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Exports the JsonMap class as the default export of this module
|
|
67
|
+
exports.JsonMap = JsonMap;
|
|
68
|
+
async function _transform2(node, input, output) {
|
|
69
|
+
let path = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '';
|
|
70
|
+
// Checks if the current node is an object and has a '$' key
|
|
71
|
+
if ((0, _isObject2.default)(node) && '$' in node) {
|
|
72
|
+
// Retrieves the transformations to be applied (can be an array or a single object)
|
|
73
|
+
const transformations = (0, _isArray2.default)(node['$']) ? node['$'] : [node['$']];
|
|
74
|
+
|
|
75
|
+
// Array to store the results of the transformations
|
|
76
|
+
let results = [];
|
|
77
|
+
|
|
78
|
+
// Iterates over each transformation
|
|
79
|
+
for (const transformation of transformations) {
|
|
80
|
+
// Resolves the object path for the transformation
|
|
81
|
+
const object = _classPrivateMethodGet(this, _resolvePath, _resolvePath2).call(this, transformation.object, results);
|
|
82
|
+
|
|
83
|
+
// Resolves the parameter paths for the transformation
|
|
84
|
+
const params = await Promise.all((0, _castArray2.default)(transformation.params).map(param => _classPrivateMethodGet(this, _resolvePath, _resolvePath2).call(this, param, results)));
|
|
85
|
+
|
|
86
|
+
// Calls the specified method on the resolved object with the resolved parameters
|
|
87
|
+
const result = await object[transformation.method](...params);
|
|
88
|
+
|
|
89
|
+
// Stores the result of the transformation
|
|
90
|
+
results.push(result);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Sets the output at the specified path to the last result of the transformations & returns.
|
|
94
|
+
const last = (0, _last2.default)(results);
|
|
95
|
+
(0, _set2.default)(output, path, last);
|
|
96
|
+
return last;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Checks if the current node is an object
|
|
100
|
+
if ((0, _isObject2.default)(node)) {
|
|
101
|
+
// Creates an empty array or object based on whether the current node is an array or not
|
|
102
|
+
const transformedNode = (0, _isArray2.default)(node) ? [] : {};
|
|
103
|
+
|
|
104
|
+
// Iterates over each key-value pair in the current node
|
|
105
|
+
for (const [key, value] of Object.entries(node)) {
|
|
106
|
+
// Constructs the current path by appending the current key to the previous path (if any)
|
|
107
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
108
|
+
|
|
109
|
+
// Recursively calls #transform with the current value, input, output, and path
|
|
110
|
+
// Assigns the transformed value to the corresponding key in the transformedNode
|
|
111
|
+
transformedNode[key] = await _classPrivateMethodGet(this, _transform, _transform2).call(this, value, input, output, currentPath);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Sets the output at the specified path to the transformedNode & returnsd.
|
|
115
|
+
(0, _set2.default)(output, path, transformedNode);
|
|
116
|
+
return transformedNode;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Sets the output at the specified path to the current node & returns.
|
|
120
|
+
(0, _set2.default)(output, path, node);
|
|
121
|
+
return node;
|
|
122
|
+
}
|
|
123
|
+
function _resolvePath2(path, results) {
|
|
124
|
+
// If the path is not a string, return it as is
|
|
125
|
+
if (!(0, _isString2.default)(path)) {
|
|
126
|
+
return path;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Defines special patterns and their corresponding values for resolution
|
|
130
|
+
const specialPatterns = {
|
|
131
|
+
'$.lib': this.lib,
|
|
132
|
+
'$.input': this.input,
|
|
133
|
+
'$.output': this.output
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Checks if the path matches the pattern for accessing previous results
|
|
137
|
+
const match = path.match(/^\$\[(\d+)\](.*)$/);
|
|
138
|
+
|
|
139
|
+
// Retrieves the value from the previous results based on the index and any remaining path
|
|
140
|
+
if (match) {
|
|
141
|
+
const [, index, rest] = match;
|
|
142
|
+
const value = (0, _get2.default)(results, [results.length - 1 - (0, _parseInt2.default)(index), ...(rest.length ? rest.split('.') : [])]);
|
|
143
|
+
|
|
144
|
+
// Returns the value if it exists, otherwise returns the original path
|
|
145
|
+
return value != null ? value : path;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Iterates over the special patterns
|
|
149
|
+
for (const [pattern, replacement] of Object.entries(specialPatterns)) if (path.startsWith(pattern)) {
|
|
150
|
+
// Removes the pattern from the beginning of the path
|
|
151
|
+
const p = path.slice(pattern.length + 1);
|
|
152
|
+
|
|
153
|
+
// Retrieves the value from the replacement object based on the remaining path
|
|
154
|
+
const value = p.length ? (0, _get2.default)(replacement, p.split('.')) : replacement;
|
|
155
|
+
|
|
156
|
+
// Returns the value if it exists, otherwise returns the original path
|
|
157
|
+
return (0, _isNil2.default)(value) ? path : value;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Returns the path as is if it does not match any special patterns
|
|
161
|
+
return path;
|
|
162
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "JsonMap", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _JsonMap.JsonMap;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
var _JsonMap = require("./JsonMap/JsonMap.js");
|
package/env/.env.dev
ADDED
package/env/dynamic.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
() => ({});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* JsonMap class to apply transformations to a JSON object
|
|
5
|
+
*/
|
|
6
|
+
class JsonMap {
|
|
7
|
+
/**
|
|
8
|
+
* Creates an instance of JsonMap.
|
|
9
|
+
*
|
|
10
|
+
* @param {object} lib - A collection of function libraries.
|
|
11
|
+
* @param {object} map - The data mapping configuration.
|
|
12
|
+
*/
|
|
13
|
+
constructor(lib, map) {
|
|
14
|
+
this.lib = lib;
|
|
15
|
+
this.map = map;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Transforms the input data according to the map configuration.
|
|
20
|
+
*
|
|
21
|
+
* @param {object} input - The input data to be transformed.
|
|
22
|
+
* @returns {object} - The transformed data.
|
|
23
|
+
*/
|
|
24
|
+
async transform(input) {
|
|
25
|
+
// Sets the input data and initializes an empty output object
|
|
26
|
+
this.input = input;
|
|
27
|
+
this.output = {};
|
|
28
|
+
|
|
29
|
+
// Calls the #transform method to perform the transformation
|
|
30
|
+
return await this.#transform(this.map, this.input, this.output);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Recursive function to handle transformations.
|
|
35
|
+
*
|
|
36
|
+
* @param {object} node - The current map node.
|
|
37
|
+
* @param {object} input - The current input node.
|
|
38
|
+
* @param {object} output - The current output node.
|
|
39
|
+
* @param {string} path - The path to the current node.
|
|
40
|
+
* @returns {object} - The transformed node.
|
|
41
|
+
* @private
|
|
42
|
+
*/
|
|
43
|
+
async #transform(node, input, output, path = '') {
|
|
44
|
+
// Checks if the current node is an object and has a '$' key
|
|
45
|
+
if (_.isObject(node) && '$' in node) {
|
|
46
|
+
// Retrieves the transformations to be applied (can be an array or a single object)
|
|
47
|
+
const transformations = _.isArray(node['$']) ? node['$'] : [node['$']];
|
|
48
|
+
|
|
49
|
+
// Array to store the results of the transformations
|
|
50
|
+
let results = [];
|
|
51
|
+
|
|
52
|
+
// Iterates over each transformation
|
|
53
|
+
for (const transformation of transformations) {
|
|
54
|
+
// Resolves the object path for the transformation
|
|
55
|
+
const object = this.#resolvePath(transformation.object, results);
|
|
56
|
+
|
|
57
|
+
// Resolves the parameter paths for the transformation
|
|
58
|
+
const params = await Promise.all(
|
|
59
|
+
_.castArray(transformation.params).map((param) =>
|
|
60
|
+
this.#resolvePath(param, results)
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Calls the specified method on the resolved object with the resolved parameters
|
|
65
|
+
const result = await object[transformation.method](...params);
|
|
66
|
+
|
|
67
|
+
// Stores the result of the transformation
|
|
68
|
+
results.push(result);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Sets the output at the specified path to the last result of the transformations & returns.
|
|
72
|
+
const last = _.last(results);
|
|
73
|
+
_.set(output, path, last);
|
|
74
|
+
return last;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Checks if the current node is an object
|
|
78
|
+
if (_.isObject(node)) {
|
|
79
|
+
// Creates an empty array or object based on whether the current node is an array or not
|
|
80
|
+
const transformedNode = _.isArray(node) ? [] : {};
|
|
81
|
+
|
|
82
|
+
// Iterates over each key-value pair in the current node
|
|
83
|
+
for (const [key, value] of Object.entries(node)) {
|
|
84
|
+
// Constructs the current path by appending the current key to the previous path (if any)
|
|
85
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
86
|
+
|
|
87
|
+
// Recursively calls #transform with the current value, input, output, and path
|
|
88
|
+
// Assigns the transformed value to the corresponding key in the transformedNode
|
|
89
|
+
transformedNode[key] = await this.#transform(
|
|
90
|
+
value,
|
|
91
|
+
input,
|
|
92
|
+
output,
|
|
93
|
+
currentPath
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Sets the output at the specified path to the transformedNode & returnsd.
|
|
98
|
+
_.set(output, path, transformedNode);
|
|
99
|
+
return transformedNode;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Sets the output at the specified path to the current node & returns.
|
|
103
|
+
_.set(output, path, node);
|
|
104
|
+
return node;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Resolves the object/method/params path for a transformation
|
|
109
|
+
*
|
|
110
|
+
* @param {string} path - The path to be resolved.
|
|
111
|
+
* @param {Array} results - The results from previous transformations.
|
|
112
|
+
* @return {string} - The resolved path.
|
|
113
|
+
* @private
|
|
114
|
+
*/
|
|
115
|
+
#resolvePath(path, results) {
|
|
116
|
+
// If the path is not a string, return it as is
|
|
117
|
+
if (!_.isString(path)) {
|
|
118
|
+
return path;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Defines special patterns and their corresponding values for resolution
|
|
122
|
+
const specialPatterns = {
|
|
123
|
+
'$.lib': this.lib,
|
|
124
|
+
'$.input': this.input,
|
|
125
|
+
'$.output': this.output,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Checks if the path matches the pattern for accessing previous results
|
|
129
|
+
const match = path.match(/^\$\[(\d+)\](.*)$/);
|
|
130
|
+
|
|
131
|
+
// Retrieves the value from the previous results based on the index and any remaining path
|
|
132
|
+
if (match) {
|
|
133
|
+
const [, index, rest] = match;
|
|
134
|
+
const value = _.get(results, [
|
|
135
|
+
results.length - 1 - _.parseInt(index),
|
|
136
|
+
...(rest.length ? rest.split('.') : []),
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
// Returns the value if it exists, otherwise returns the original path
|
|
140
|
+
return value != null ? value : path;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Iterates over the special patterns
|
|
144
|
+
for (const [pattern, replacement] of Object.entries(specialPatterns))
|
|
145
|
+
if (path.startsWith(pattern)) {
|
|
146
|
+
// Removes the pattern from the beginning of the path
|
|
147
|
+
const p = path.slice(pattern.length + 1);
|
|
148
|
+
|
|
149
|
+
// Retrieves the value from the replacement object based on the remaining path
|
|
150
|
+
const value = p.length ? _.get(replacement, p.split('.')) : replacement;
|
|
151
|
+
|
|
152
|
+
// Returns the value if it exists, otherwise returns the original path
|
|
153
|
+
return _.isNil(value) ? path : value;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Returns the path as is if it does not match any special patterns
|
|
157
|
+
return path;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Exports the JsonMap class as the default export of this module
|
|
162
|
+
export { JsonMap };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { JsonMap } from './JsonMap/JsonMap.js';
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@karmaniverous/jsonmap",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/karmaniverous/jsonmap"
|
|
10
|
+
},
|
|
11
|
+
"author": "Jason G. Williscroft",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/karmaniverous/jsonmap/issues"
|
|
14
|
+
},
|
|
15
|
+
"description": "A hyper-generic JSON mapping library.",
|
|
16
|
+
"homepage": "https://github.com/karmaniverous/jsonmap#readme",
|
|
17
|
+
"keywords": [
|
|
18
|
+
"json",
|
|
19
|
+
"map",
|
|
20
|
+
"es6",
|
|
21
|
+
"javascript"
|
|
22
|
+
],
|
|
23
|
+
"license": "BSD-3-Clause",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"lodash": "^4.17.21"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@babel/cli": "^7.21.0",
|
|
29
|
+
"@babel/core": "^7.21.3",
|
|
30
|
+
"@babel/eslint-parser": "^7.21.3",
|
|
31
|
+
"@babel/plugin-syntax-import-assertions": "^7.20.0",
|
|
32
|
+
"@babel/preset-env": "^7.20.2",
|
|
33
|
+
"@babel/register": "^7.21.0",
|
|
34
|
+
"@karmaniverous/get-dotenv": "^1.0.0",
|
|
35
|
+
"@types/node": "^18.15.5",
|
|
36
|
+
"babel-plugin-lodash": "^3.3.4",
|
|
37
|
+
"chai": "^4.3.7",
|
|
38
|
+
"concat-md": "^0.5.1",
|
|
39
|
+
"eslint": "^8.36.0",
|
|
40
|
+
"eslint-config-standard": "^17.0.0",
|
|
41
|
+
"eslint-plugin-jsdoc": "^40.1.0",
|
|
42
|
+
"eslint-plugin-mocha": "^10.1.0",
|
|
43
|
+
"jsdoc-to-markdown": "^8.0.0",
|
|
44
|
+
"mocha": "^10.2.0",
|
|
45
|
+
"numeral": "^2.0.6",
|
|
46
|
+
"prettier": "^2.8.5",
|
|
47
|
+
"release-it": "^15.9.0"
|
|
48
|
+
},
|
|
49
|
+
"exports": {
|
|
50
|
+
".": {
|
|
51
|
+
"import": "./lib/index.js",
|
|
52
|
+
"require": "./dist/default/lib/index.js"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"main": "./lib/index.js",
|
|
56
|
+
"mocha": {
|
|
57
|
+
"exclude": [
|
|
58
|
+
"./dist/**",
|
|
59
|
+
"./node_modules/**"
|
|
60
|
+
],
|
|
61
|
+
"file": "./test/setup.js",
|
|
62
|
+
"require": [
|
|
63
|
+
"@babel/register"
|
|
64
|
+
],
|
|
65
|
+
"spec": "./**/*.test.!(*.*)"
|
|
66
|
+
},
|
|
67
|
+
"release-it": {
|
|
68
|
+
"github": {
|
|
69
|
+
"release": true
|
|
70
|
+
},
|
|
71
|
+
"npm": {
|
|
72
|
+
"publish": true
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"scripts": {
|
|
76
|
+
"build": "babel lib -d dist/default/lib --delete-dir-on-start --config-file ./dist/default/.babelrc",
|
|
77
|
+
"doc": "jsdoc2md -c doc/jsdoc.config.json -f lib/**/*.* -t doc/api-template.hbs > doc/2-api.jsdoc2.md && concat-md doc --hide-anchor-links > README.md",
|
|
78
|
+
"lint": "eslint lib/**",
|
|
79
|
+
"package": "npm run lint && npm run test && npm run build && npm run doc",
|
|
80
|
+
"release": "npm run package && getdotenv -- release-it",
|
|
81
|
+
"test": "getdotenv -c \"mocha\" -p ./ ./env -d dev -y ./env/dynamic.js"
|
|
82
|
+
},
|
|
83
|
+
"type": "module"
|
|
84
|
+
}
|
package/prompt.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
Here is a usage example of a Javascript class called JsonMap that applies transformations to a JSON object. The class should exploit the lodash library where relevant.
|
|
2
|
+
|
|
3
|
+
```js
|
|
4
|
+
import _ from 'lodash';
|
|
5
|
+
import numeral from 'numeral';
|
|
6
|
+
import { JsonMap } from 'json-map';
|
|
7
|
+
|
|
8
|
+
const fetchData = async (entityToken, entityId) => {
|
|
9
|
+
// This async code fetches data from an API.
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// This object is a collection of function libraries.
|
|
13
|
+
const lib = { _, fetchData, numeral, String };
|
|
14
|
+
|
|
15
|
+
// This is the data mapping configuration.
|
|
16
|
+
// The output will have the same structure as this object.
|
|
17
|
+
const map = {
|
|
18
|
+
txn: {
|
|
19
|
+
txnId: {
|
|
20
|
+
/*
|
|
21
|
+
An object value with a single key of $ indicates a transformation.
|
|
22
|
+
|
|
23
|
+
A transformation is composed of steps. Each step is an object with 3 keys: object, method, and params. Each transformation step runs a method of an object, optionally passing it a parameter array.
|
|
24
|
+
|
|
25
|
+
If the $ value is an object, there is a single transformation step. Multiple steps can be collected into an array.
|
|
26
|
+
*/
|
|
27
|
+
$: {
|
|
28
|
+
/*
|
|
29
|
+
The object property itentifies the object whose method will be run. The object is resolved using a special syntax:
|
|
30
|
+
|
|
31
|
+
$.lib refers to the JsonMap instance lib property.
|
|
32
|
+
|
|
33
|
+
$.input refers to the input data object.
|
|
34
|
+
|
|
35
|
+
$.output refers to the output object. The transformation is progressive, so the value currently being processed can refer to previously processed values.
|
|
36
|
+
|
|
37
|
+
$[i], where i is an integer, refers to the ith previous transformation step, so $[0] is the last step, $[1] is the one before that, and so on. This syntax should account for the fact that a previous step might return an object, in which case $[i] might be followed by path information, e.g. $[0].path.
|
|
38
|
+
|
|
39
|
+
Beyond these base objects, lodash-style path syntax applies. If none of the patterns above fits, then object should be treated as a string.
|
|
40
|
+
*/
|
|
41
|
+
object: '$.lib._',
|
|
42
|
+
|
|
43
|
+
/*
|
|
44
|
+
The method property identifies the method that will be executed on object. It should be a string.
|
|
45
|
+
*/
|
|
46
|
+
method: 'get',
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
The params property is an array of values to be passed to method. These values should be resolved using the same syntax as object. If none of the special patterns fits, then the value should be treated as a string.
|
|
50
|
+
*/
|
|
51
|
+
params: ['$.input', 'dynamodb.NewImage.txnId.S'],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
roundup: {
|
|
55
|
+
$: [
|
|
56
|
+
{
|
|
57
|
+
object: '$.lib._',
|
|
58
|
+
method: 'get',
|
|
59
|
+
params: ['$.input', 'dynamodb.NewImage.roundup.N'],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
object: '$.lib',
|
|
63
|
+
method: 'numeral',
|
|
64
|
+
// If there is only a single param, no array is necessary.
|
|
65
|
+
params: '$[0]',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
object: '$[0]',
|
|
69
|
+
method: 'format',
|
|
70
|
+
params: ['$0,0.00'],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
// Static values & structures will simply be passed through to the output.
|
|
75
|
+
foo: 'bar',
|
|
76
|
+
baz: {
|
|
77
|
+
object: '$.lib.String',
|
|
78
|
+
method: 'toUpperCase',
|
|
79
|
+
params: '$.foo',
|
|
80
|
+
},
|
|
81
|
+
fetch: {
|
|
82
|
+
object: '$.lib',
|
|
83
|
+
// This is an async function that returns an object!
|
|
84
|
+
method: 'fetchData',
|
|
85
|
+
// The second parameter leverages a previously processed value.
|
|
86
|
+
params: ['txn', '$.output.txn.txnId'],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// This is some sample input data to be transformed.
|
|
92
|
+
const input = {
|
|
93
|
+
eventID: 'e560bbcaee919e16a6a8ce8dc3fe97ab',
|
|
94
|
+
eventName: 'MODIFY',
|
|
95
|
+
eventVersion: '1.1',
|
|
96
|
+
eventSource: 'aws:dynamodb',
|
|
97
|
+
awsRegion: 'us-east-1',
|
|
98
|
+
dynamodb: {
|
|
99
|
+
ApproximateCreationDateTime: 1685071094,
|
|
100
|
+
Keys: {
|
|
101
|
+
entityPK: {
|
|
102
|
+
S: 'txn!',
|
|
103
|
+
},
|
|
104
|
+
entitySK: {
|
|
105
|
+
S: 'txnId#_tQ72mu5Iy2PYJ33c497g',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
NewImage: {
|
|
109
|
+
txnUserIdPK: {
|
|
110
|
+
S: 'txn!|userId#Xmv51c7lYiiTIU22_UlCD',
|
|
111
|
+
},
|
|
112
|
+
created: {
|
|
113
|
+
N: '1685071087846',
|
|
114
|
+
},
|
|
115
|
+
netValue: {
|
|
116
|
+
N: '365.3',
|
|
117
|
+
},
|
|
118
|
+
methodId: {
|
|
119
|
+
S: 'KDvC6leEzkAAf8-akh_vO',
|
|
120
|
+
},
|
|
121
|
+
entityPK: {
|
|
122
|
+
S: 'txn!',
|
|
123
|
+
},
|
|
124
|
+
userId: {
|
|
125
|
+
S: 'Xmv51c7lYiiTIU22_UlCD',
|
|
126
|
+
},
|
|
127
|
+
entitySK: {
|
|
128
|
+
S: 'txnId#_tQ72mu5Iy2PYJ33c497g',
|
|
129
|
+
},
|
|
130
|
+
updater: {
|
|
131
|
+
S: 'api-txn-v0-bali',
|
|
132
|
+
},
|
|
133
|
+
merchantId: {
|
|
134
|
+
S: 'eQMZ2ikPmUd3cqrptbpna',
|
|
135
|
+
},
|
|
136
|
+
roundup: {
|
|
137
|
+
N: '0.7',
|
|
138
|
+
},
|
|
139
|
+
txnMerchantIdPK: {
|
|
140
|
+
S: 'txn!|merchantId#eQMZ2ikPmUd3cqrptbpna',
|
|
141
|
+
},
|
|
142
|
+
updated: {
|
|
143
|
+
N: '1685071094685',
|
|
144
|
+
},
|
|
145
|
+
txnId: {
|
|
146
|
+
S: '_tQ72mu5Iy2PYJ33c497g',
|
|
147
|
+
},
|
|
148
|
+
txnMethodIdPK: {
|
|
149
|
+
S: 'txn!|methodId#KDvC6leEzkAAf8-akh_vO',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
OldImage: {
|
|
153
|
+
txnUserIdPK: {
|
|
154
|
+
S: 'txn!|userId#Xmv51c7lYiiTIU22_UlCD',
|
|
155
|
+
},
|
|
156
|
+
created: {
|
|
157
|
+
N: '1685071087846',
|
|
158
|
+
},
|
|
159
|
+
netValue: {
|
|
160
|
+
N: '429.74',
|
|
161
|
+
},
|
|
162
|
+
methodId: {
|
|
163
|
+
S: 'KDvC6leEzkAAf8-akh_vO',
|
|
164
|
+
},
|
|
165
|
+
entityPK: {
|
|
166
|
+
S: 'txn!',
|
|
167
|
+
},
|
|
168
|
+
userId: {
|
|
169
|
+
S: 'Xmv51c7lYiiTIU22_UlCD',
|
|
170
|
+
},
|
|
171
|
+
entitySK: {
|
|
172
|
+
S: 'txnId#_tQ72mu5Iy2PYJ33c497g',
|
|
173
|
+
},
|
|
174
|
+
updater: {
|
|
175
|
+
S: 'api-txn-v0-bali',
|
|
176
|
+
},
|
|
177
|
+
merchantId: {
|
|
178
|
+
S: 'eQMZ2ikPmUd3cqrptbpna',
|
|
179
|
+
},
|
|
180
|
+
roundup: {
|
|
181
|
+
N: '0.26',
|
|
182
|
+
},
|
|
183
|
+
txnMerchantIdPK: {
|
|
184
|
+
S: 'txn!|merchantId#eQMZ2ikPmUd3cqrptbpna',
|
|
185
|
+
},
|
|
186
|
+
updated: {
|
|
187
|
+
N: '1685071087846',
|
|
188
|
+
},
|
|
189
|
+
txnId: {
|
|
190
|
+
S: '_tQ72mu5Iy2PYJ33c497g',
|
|
191
|
+
},
|
|
192
|
+
txnMethodIdPK: {
|
|
193
|
+
S: 'txn!|methodId#KDvC6leEzkAAf8-akh_vO',
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
SequenceNumber: '31120900000000039175922358',
|
|
197
|
+
SizeBytes: 801,
|
|
198
|
+
StreamViewType: 'NEW_AND_OLD_IMAGES',
|
|
199
|
+
},
|
|
200
|
+
eventSourceARN:
|
|
201
|
+
'arn:aws:dynamodb:us-east-1:546652796775:table/api-txn-v0-bali/stream/2023-05-19T12:57:41.682',
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// This initializes a JsonMap instance.
|
|
205
|
+
const jsonMap = new JsonMap(lib, map);
|
|
206
|
+
|
|
207
|
+
// This transforms the sample input data.
|
|
208
|
+
const result = jsonMap.transform(input);
|
|
209
|
+
|
|
210
|
+
// This is the result!
|
|
211
|
+
// {
|
|
212
|
+
// txn: {
|
|
213
|
+
// txnId: '_tQ72mu5Iy2PYJ33c497g',
|
|
214
|
+
// roundup: '$0.70',
|
|
215
|
+
// },
|
|
216
|
+
// foo: 'bar',
|
|
217
|
+
// baz: 'BAR',
|
|
218
|
+
// fetch: {
|
|
219
|
+
// statusCode: 200,
|
|
220
|
+
// message: 'asynchronously fetched data!'
|
|
221
|
+
// }
|
|
222
|
+
// }
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The class should accommodate deep placement of transformations within the map object. It should also recursively account for transformation objects that resolve into transformation objects.
|
|
226
|
+
|
|
227
|
+
Fully implement the JsonMap class with jsdoc comments.
|