jtlt 0.2.0 → 0.4.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/CHANGES.md +27 -0
- package/README.md +61 -164
- package/demo/calltemplate-params-demo.js +138 -0
- package/demo/codemirror.esm.js +28242 -0
- package/demo/codemirror.js +94 -0
- package/demo/index.css +7 -0
- package/demo/index.html +28 -0
- package/demo/index.js +210 -0
- package/demo/vendor/jamilih/dist/jml.mjs +2341 -0
- package/demo/vendor/jhtml/src/SAJJ/SAJJ.ObjectArrayDelegator.js +356 -0
- package/demo/vendor/jhtml/src/SAJJ/SAJJ.Stringifier.js +186 -0
- package/demo/vendor/jhtml/src/SAJJ/SAJJ.js +746 -0
- package/demo/vendor/jhtml/src/SAJJ/testing/SAJJ.html +33 -0
- package/demo/vendor/jhtml/src/SAJJ/testing/SAJJ.testing.js +25 -0
- package/demo/vendor/jhtml/src/jhtml-browser.js +5 -0
- package/demo/vendor/jhtml/src/jhtml-node.cts +3 -0
- package/demo/vendor/jhtml/src/jhtml-node.js +8 -0
- package/demo/vendor/jhtml/src/jhtml-node.mts +1 -0
- package/demo/vendor/jhtml/src/jhtml.cts +3 -0
- package/demo/vendor/jhtml/src/jhtml.js +602 -0
- package/demo/vendor/jhtml/src/jhtml.mts +1 -0
- package/demo/vendor/jsonpath-plus/dist/index-browser-esm.js +2158 -0
- package/demo/vendor/simple-get-json/dist/index-es.js +151 -0
- package/demo/xpath2-placeholder.js +1 -0
- package/dist/AbstractJoiningTransformer.d.ts +83 -9
- package/dist/AbstractJoiningTransformer.d.ts.map +1 -1
- package/dist/DOMJoiningTransformer.d.ts +85 -25
- package/dist/DOMJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONJoiningTransformer.d.ts +159 -51
- package/dist/JSONJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformer.d.ts +37 -38
- package/dist/JSONPathTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformerContext.d.ts +334 -124
- package/dist/JSONPathTransformerContext.d.ts.map +1 -1
- package/dist/StringJoiningTransformer.d.ts +132 -41
- package/dist/StringJoiningTransformer.d.ts.map +1 -1
- package/dist/XPathTransformer.d.ts +35 -20
- package/dist/XPathTransformer.d.ts.map +1 -1
- package/dist/XPathTransformerContext.d.ts +281 -105
- package/dist/XPathTransformerContext.d.ts.map +1 -1
- package/dist/index-browser.d.ts +4 -0
- package/dist/index-browser.d.ts.map +1 -0
- package/dist/index-node.d.ts +4 -0
- package/dist/index-node.d.ts.map +1 -0
- package/dist/index.d.ts +330 -57
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +204 -0
- package/dist/types.d.ts.map +1 -0
- package/docs/API.expanded.md +172 -5
- package/docs/API.md +92 -2
- package/docs/TO-DO.md +168 -0
- package/docs/calltemplate-params.md +251 -0
- package/eslint.config.js +11 -5
- package/package.json +35 -9
- package/pnpm-workspace.yaml +1 -0
- package/rollup.config.js +13 -0
- package/src/AbstractJoiningTransformer.js +54 -15
- package/src/DOMJoiningTransformer.js +275 -28
- package/src/JSONJoiningTransformer.js +351 -70
- package/src/JSONPathTransformer.js +48 -30
- package/src/JSONPathTransformerContext.js +729 -107
- package/src/StringJoiningTransformer.js +311 -57
- package/src/XPathTransformer.js +27 -12
- package/src/XPathTransformerContext.js +928 -99
- package/src/index-browser.js +5 -0
- package/src/index-node.js +7 -0
- package/src/index.js +502 -98
- package/tsconfig.json +5 -2
- package/typings/xpath2-js.d.ts +40 -1
- package/src/types/xpath2-js.d.ts +0 -2
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAJJ Simple API for JSON/JavaScript objects
|
|
3
|
+
* This is not intended as a streaming string parser, though `walkJSONString()`
|
|
4
|
+
* is provided for whole strings;
|
|
5
|
+
* Clarinet (https://github.com/dscape/clarinet ) is more likely the better choice for such cases.
|
|
6
|
+
* SAJJ, as with SAX, could be adapted to allow DOM TreeWalker-style parsing
|
|
7
|
+
* (pull or automatic cycling: todo) along with
|
|
8
|
+
* XSL-style iteration (though optional whether to ultimately replace original
|
|
9
|
+
* content), e.g., for use with Jamilih or JsonML style (see JTLT project)
|
|
10
|
+
* templates (or enhanced via full JS with event handlers).
|
|
11
|
+
* SampleUseCases
|
|
12
|
+
* 1) Converting JavaScript structures to JSON
|
|
13
|
+
* 2) Implementing a SAX-like parser over XML-as-JSON solutions like Jamilih
|
|
14
|
+
* or JsonML
|
|
15
|
+
* 3) XSL-like transformations of JSON (or XML-as-JSON), e.g., to JHTML
|
|
16
|
+
* SampleImplementations
|
|
17
|
+
* 1. Conversion of JSON to JHTML
|
|
18
|
+
* 2. JSON.stringify() (Todo: support replacer and space arguments)
|
|
19
|
+
* DesignGoals
|
|
20
|
+
* 1. Accurate, easy to use, small, fast, memory-efficient, universal in
|
|
21
|
+
* coverage, clean code
|
|
22
|
+
* 2. Convenient (e.g., with overridable methods) but not auto-creating
|
|
23
|
+
* likely useful polyfills like Object.keys(), Object.getOwnPropertyNames(),
|
|
24
|
+
* JSON, etc. Might reconsider
|
|
25
|
+
* optionally auto-exporting them, or adding as handler arguments, in the
|
|
26
|
+
* future, but not planning for now.
|
|
27
|
+
* 3. Context-aware (handlers to include parent objects as well as values
|
|
28
|
+
* or JSONPaths)
|
|
29
|
+
* 4. Customizable: Ability to override/customize any functionality and allow
|
|
30
|
+
* custom types but without need for reimplementing iteration routines
|
|
31
|
+
* 5. Offer optional support of regular JavaScript objects (including those
|
|
32
|
+
* potentially representing XML/HTML with events)
|
|
33
|
+
* 6. Allow pull or auto-push reporting
|
|
34
|
+
* 7. Configuration vis-a-vis Clarinet/sax-js options:
|
|
35
|
+
* a) Decided for now against trim/normalize options as in Clarinet as
|
|
36
|
+
* seemed not very useful, though could be allowed easily in stringHandler
|
|
37
|
+
* b) lowercase and xmlns seem too XML-specific
|
|
38
|
+
* c) position has analogue in JSONPath goal
|
|
39
|
+
* 8. Decided against causing conversion to string and feeding into Clarinet
|
|
40
|
+
* (or `JSON.parse(obj, reviver);`) as use cases of beginning with JSON
|
|
41
|
+
* rather than merely converting to it were too great (toward JS as main
|
|
42
|
+
* environment or even content-type).
|
|
43
|
+
* 9. Decided against Clarinet handler names as considered ugly relative to
|
|
44
|
+
* CamelCase (despite JS-event-style-familiarity) though
|
|
45
|
+
* I may provide adapters later (todo)
|
|
46
|
+
* 10. Decided against passing Object.keys (or other exports of Object
|
|
47
|
+
* properties like getOwnPropertyNames) to
|
|
48
|
+
* beginObjectHandler/beginArrayHandler (and corresponding end methods) as
|
|
49
|
+
* auto-iteration of keys/values ought to address most use cases for
|
|
50
|
+
* obtaining all keys and user can do it themselves if needed. We did pass
|
|
51
|
+
* length of array to begin and endArrayHandler, however.
|
|
52
|
+
* 11. Have module support standard export formats
|
|
53
|
+
* 12. Demonstrate functionality by implementing JSON.stringify though provide
|
|
54
|
+
* empty version
|
|
55
|
+
*
|
|
56
|
+
* PossibleFutureTodos
|
|
57
|
+
* 1. Add references to jml() in docs along with JsonML references
|
|
58
|
+
* 2. Integrate with allowing stream input as in Clarinet?
|
|
59
|
+
* 3. TreeWalker/NodeIterator equivalents?
|
|
60
|
+
* 4. Add array-extra methods along with functional join?
|
|
61
|
+
*
|
|
62
|
+
* @todo
|
|
63
|
+
*
|
|
64
|
+
* 1. Infinity, NaN, String, Number, Date, etc.
|
|
65
|
+
* 2. Add depth level `@property` (which could be used, e.g., by a
|
|
66
|
+
* JSON.stringify implementation)
|
|
67
|
+
* a) Implement JSON.stringify (without calling JSON.stringify!); if
|
|
68
|
+
* not, fix SampleImplementations above
|
|
69
|
+
* i) Finish array/object (call delegateHandlersByType inside
|
|
70
|
+
* keyValueHandler or in object/arrayHandler?; change keyValueHandlers
|
|
71
|
+
* to return commas, etc.)
|
|
72
|
+
* ii) avoid functions/undefined/prototype completely, and converting
|
|
73
|
+
* nonfinite to null
|
|
74
|
+
* 3. Add JSONPaths (or implement JSONPath reporting in SAJJ as in
|
|
75
|
+
* jsonPath())? XPath to string SAX XML? .getXPath on DOM node prototype?
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/* eslint-disable jsdoc/reject-any-type -- Arbitrary */
|
|
79
|
+
/**
|
|
80
|
+
* @typedef {any} AnyValue
|
|
81
|
+
*/
|
|
82
|
+
/* eslint-enable jsdoc/reject-any-type -- Arbitrary */
|
|
83
|
+
|
|
84
|
+
/* eslint-disable jsdoc/reject-function-type -- Generic */
|
|
85
|
+
/**
|
|
86
|
+
* @typedef {Function} GenericFunction
|
|
87
|
+
*/
|
|
88
|
+
/* eslint-enable jsdoc/reject-function-type -- Generic */
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @typedef {undefined | null | boolean | number | string |
|
|
92
|
+
* GenericFunction} NonObject
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @typedef {{
|
|
97
|
+
* [key: string]: NonObject | NestedObject | NestedObject[]
|
|
98
|
+
* }} NestedObject
|
|
99
|
+
*/
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @typedef {"undefined"|"null"|"boolean"|"symbol"|
|
|
103
|
+
* "number"|"nonfiniteNumber"|"bigint"|
|
|
104
|
+
* "string"|"function"|"array"|"object"|"ignore"} SAJJType
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
// PRIVATE STATIC UTILITIES
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Make a shallow or deep copy of an object.
|
|
111
|
+
* @private
|
|
112
|
+
* @constant
|
|
113
|
+
* @param {NestedObject} obj Object to copy
|
|
114
|
+
* @param {boolean} [deep] Whether or not to make a deep copy. Defaults to false
|
|
115
|
+
* @returns {NestedObject|NonObject} Copied object
|
|
116
|
+
*/
|
|
117
|
+
function _copyObject (obj, deep) {
|
|
118
|
+
/** @type {NestedObject} */
|
|
119
|
+
const copyObj = {};
|
|
120
|
+
// eslint-disable-next-line guard-for-in -- Deliberate iterating of prototype
|
|
121
|
+
for (const prop in obj) {
|
|
122
|
+
copyObj[prop] = deep && obj[prop] && typeof obj[prop] === 'object'
|
|
123
|
+
? _copyObject(/** @type {NestedObject} */ (obj[prop]))
|
|
124
|
+
: obj[prop];
|
|
125
|
+
}
|
|
126
|
+
return copyObj;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// GENERIC JSON/JS CONSTRUCTOR
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @typedef {{
|
|
133
|
+
* mode?: "JSON"|"JavaScript",
|
|
134
|
+
* distinguishKeysValues?: boolean,
|
|
135
|
+
* iterateArrays?: boolean,
|
|
136
|
+
* iterateObjects?: boolean,
|
|
137
|
+
* iterateObjectPrototype?: boolean,
|
|
138
|
+
* iterateArrayPrototype?: boolean,
|
|
139
|
+
* delegateHandlers?: DelegateHandlers
|
|
140
|
+
* parentKey?: string,
|
|
141
|
+
* parentObject?: object,
|
|
142
|
+
* parentObjectArrayBool?: boolean,
|
|
143
|
+
* }} SAJJOptions
|
|
144
|
+
*/
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @todo Support object + JSONPath as first argument for iteration within
|
|
148
|
+
* a larger tree
|
|
149
|
+
*/
|
|
150
|
+
class SAJJ {
|
|
151
|
+
ret = '';
|
|
152
|
+
|
|
153
|
+
/* eslint-disable jsdoc/require-returns-check -- Abstract */
|
|
154
|
+
/**
|
|
155
|
+
* Could override for logging; meant for allowing dropping of
|
|
156
|
+
* properties/methods, e.g., undefined/functions, as done, for
|
|
157
|
+
* example, by `JSON.stringify`.
|
|
158
|
+
* @param {AnyValue} obj
|
|
159
|
+
* @param {object|undefined} parentObj
|
|
160
|
+
* @param {string|undefined} parentKey
|
|
161
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
162
|
+
* @returns {string}
|
|
163
|
+
*/
|
|
164
|
+
ignoreHandler (
|
|
165
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
166
|
+
obj, parentObj, parentKey, parentObjectArrayBool
|
|
167
|
+
) {
|
|
168
|
+
throw new Error('Abstract');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @param {string} value
|
|
173
|
+
* @param {object|undefined} parentObject
|
|
174
|
+
* @param {string|undefined} parentKey
|
|
175
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
176
|
+
* @returns {string}
|
|
177
|
+
*/
|
|
178
|
+
stringHandler (
|
|
179
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
180
|
+
value, parentObject, parentKey, parentObjectArrayBool
|
|
181
|
+
) {
|
|
182
|
+
throw new Error('Abstract');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @param {number} value
|
|
187
|
+
* @param {object|undefined} parentObject
|
|
188
|
+
* @param {string|undefined} parentKey
|
|
189
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
190
|
+
* @returns {string}
|
|
191
|
+
*/
|
|
192
|
+
numberHandler (
|
|
193
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
194
|
+
value, parentObject, parentKey, parentObjectArrayBool
|
|
195
|
+
) {
|
|
196
|
+
throw new Error('Abstract');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @param {bigint} value
|
|
201
|
+
* @param {object|undefined} parentObject
|
|
202
|
+
* @param {string|undefined} parentKey
|
|
203
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
204
|
+
* @returns {string}
|
|
205
|
+
*/
|
|
206
|
+
bigintHandler (
|
|
207
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
208
|
+
value, parentObject, parentKey, parentObjectArrayBool
|
|
209
|
+
) {
|
|
210
|
+
throw new Error('Abstract');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @param {boolean} value
|
|
215
|
+
* @param {object|undefined} parentObject
|
|
216
|
+
* @param {string|undefined} parentKey
|
|
217
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
218
|
+
* @returns {string}
|
|
219
|
+
*/
|
|
220
|
+
booleanHandler (
|
|
221
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
222
|
+
value, parentObject, parentKey, parentObjectArrayBool
|
|
223
|
+
) {
|
|
224
|
+
throw new Error('Abstract');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* @param {symbol} value
|
|
229
|
+
* @param {object|undefined} parentObject
|
|
230
|
+
* @param {string|undefined} parentKey
|
|
231
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
232
|
+
* @returns {string}
|
|
233
|
+
*/
|
|
234
|
+
symbolHandler (
|
|
235
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
236
|
+
value, parentObject, parentKey, parentObjectArrayBool
|
|
237
|
+
) {
|
|
238
|
+
throw new Error('Abstract');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @param {undefined} value
|
|
243
|
+
* @param {object|undefined} parentObject
|
|
244
|
+
* @param {string|undefined} parentKey
|
|
245
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
246
|
+
* @returns {string}
|
|
247
|
+
*/
|
|
248
|
+
undefinedHandler (
|
|
249
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
250
|
+
value, parentObject, parentKey, parentObjectArrayBool
|
|
251
|
+
) {
|
|
252
|
+
throw new Error('Abstract');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* @param {AnyValue} value
|
|
257
|
+
* @param {object|undefined} parentObject
|
|
258
|
+
* @param {string|undefined} parentKey
|
|
259
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
260
|
+
* @returns {string}
|
|
261
|
+
*/
|
|
262
|
+
objectHandler (
|
|
263
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
264
|
+
value, parentObject, parentKey, parentObjectArrayBool
|
|
265
|
+
) {
|
|
266
|
+
throw new Error('Abstract');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* @param {null} obj
|
|
271
|
+
* @param {object|undefined} parentObject
|
|
272
|
+
* @param {string|undefined} parentKey
|
|
273
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
274
|
+
* @returns {string}
|
|
275
|
+
*/
|
|
276
|
+
nullHandler (
|
|
277
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
278
|
+
obj, parentObject, parentKey, parentObjectArrayBool
|
|
279
|
+
) {
|
|
280
|
+
throw new Error('Abstract');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* @param {number} value
|
|
285
|
+
* @param {object|undefined} parentObject
|
|
286
|
+
* @param {string|undefined} parentKey
|
|
287
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
288
|
+
* @returns {string}
|
|
289
|
+
*/
|
|
290
|
+
nonfiniteNumberHandler (
|
|
291
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
292
|
+
value, parentObject, parentKey, parentObjectArrayBool
|
|
293
|
+
) {
|
|
294
|
+
throw new Error('Abstract');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* @param {AnyValue[]} value
|
|
299
|
+
* @param {object|undefined} parentObject
|
|
300
|
+
* @param {string|undefined} parentKey
|
|
301
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
302
|
+
* @returns {string}
|
|
303
|
+
*/
|
|
304
|
+
arrayHandler (
|
|
305
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
306
|
+
value, parentObject, parentKey, parentObjectArrayBool
|
|
307
|
+
) {
|
|
308
|
+
throw new Error('Abstract');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @param {GenericFunction} value
|
|
313
|
+
* @param {object|undefined} parentObject
|
|
314
|
+
* @param {string|undefined} parentKey
|
|
315
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
316
|
+
* @returns {string}
|
|
317
|
+
*/
|
|
318
|
+
functionHandler (
|
|
319
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
320
|
+
value, parentObject, parentKey, parentObjectArrayBool
|
|
321
|
+
) {
|
|
322
|
+
throw new Error('Abstract');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Constructor may use this to override `keyValueHandler`.
|
|
327
|
+
* @param {AnyValue} value
|
|
328
|
+
* @param {string} key
|
|
329
|
+
* @param {object|undefined} parentObject
|
|
330
|
+
* @param {string|undefined} parentKey
|
|
331
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
332
|
+
* @param {boolean} arrayBool
|
|
333
|
+
* @param {number} iterCt
|
|
334
|
+
* @returns {string}
|
|
335
|
+
*/
|
|
336
|
+
keyValueDistinguishedHandler (
|
|
337
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
338
|
+
value, key, parentObject, parentKey,
|
|
339
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
340
|
+
parentObjectArrayBool, arrayBool, iterCt
|
|
341
|
+
) {
|
|
342
|
+
throw new Error('Abstract');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* @returns {string}
|
|
347
|
+
*/
|
|
348
|
+
arrayKeyValueJoinerHandler () {
|
|
349
|
+
throw new Error('Abstract');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* @param {object} value
|
|
354
|
+
* @param {string|number} key
|
|
355
|
+
* @param {object|undefined} parentObject
|
|
356
|
+
* @param {string|undefined} parentKey
|
|
357
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
358
|
+
* @param {boolean} arrayBool
|
|
359
|
+
* @param {number} [iterCt]
|
|
360
|
+
* @returns {string}
|
|
361
|
+
*/
|
|
362
|
+
keyValueHandler (
|
|
363
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
364
|
+
value, key, parentObject, parentKey,
|
|
365
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
366
|
+
parentObjectArrayBool, arrayBool, iterCt
|
|
367
|
+
) {
|
|
368
|
+
throw new Error('Abstract');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* @param {string|number} key
|
|
373
|
+
* @param {object|undefined} parentObject
|
|
374
|
+
* @param {string|undefined} parentKey
|
|
375
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
376
|
+
* @returns {AnyValue}
|
|
377
|
+
*/
|
|
378
|
+
arrayKeyHandler (
|
|
379
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
380
|
+
key, parentObject, parentKey, parentObjectArrayBool
|
|
381
|
+
) {
|
|
382
|
+
throw new Error('Abstract');
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* @param {string|number} key
|
|
387
|
+
* @param {object|undefined} parentObject
|
|
388
|
+
* @param {string|undefined} parentKey
|
|
389
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
390
|
+
* @param {number} [iterCt]
|
|
391
|
+
* @returns {AnyValue}
|
|
392
|
+
*/
|
|
393
|
+
objectKeyHandler (
|
|
394
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
395
|
+
key, parentObject, parentKey, parentObjectArrayBool, iterCt
|
|
396
|
+
) {
|
|
397
|
+
throw new Error('Abstract');
|
|
398
|
+
}
|
|
399
|
+
/* eslint-enable jsdoc/require-returns-check -- Abstract */
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* @param {SAJJOptions} options See setDefaultOptions() function body for
|
|
403
|
+
* some possibilities
|
|
404
|
+
*/
|
|
405
|
+
constructor (options) {
|
|
406
|
+
/** @type {SAJJOptions} */
|
|
407
|
+
// eslint-disable-next-line no-unused-expressions -- TS
|
|
408
|
+
this.options;
|
|
409
|
+
|
|
410
|
+
this.setDefaultOptions(options);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// OPTIONS
|
|
414
|
+
/**
|
|
415
|
+
* @param {SAJJOptions} [options]
|
|
416
|
+
* @returns {void}
|
|
417
|
+
*/
|
|
418
|
+
setDefaultOptions (options) {
|
|
419
|
+
const newOptions = options || {};
|
|
420
|
+
|
|
421
|
+
this.options = newOptions;
|
|
422
|
+
|
|
423
|
+
// Todo: to make properties read-only, etc., use https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperties
|
|
424
|
+
|
|
425
|
+
// CUSTOM PROPERTIES
|
|
426
|
+
// Whether to support full JavaScript objects (with functions,
|
|
427
|
+
// undefined, nonfiniteNumbers) or JSON; will not distinguish
|
|
428
|
+
// object literals from other objects, but neither does JSON.stringify
|
|
429
|
+
// which ignores prototype and drops functions/undefined and
|
|
430
|
+
// converts nonfinite to null
|
|
431
|
+
this.mode = newOptions.mode || 'JSON';
|
|
432
|
+
|
|
433
|
+
this.distinguishKeysValues = newOptions.distinguishKeysValues || false;
|
|
434
|
+
|
|
435
|
+
this.iterateArrays = newOptions.iterateArrays !== undefined
|
|
436
|
+
? newOptions.iterateArrays
|
|
437
|
+
: true;
|
|
438
|
+
this.iterateObjects = newOptions.iterateObjects !== undefined
|
|
439
|
+
? newOptions.iterateObjects
|
|
440
|
+
: true;
|
|
441
|
+
|
|
442
|
+
this.iterateObjectPrototype = newOptions.iterateObjectPrototype || false;
|
|
443
|
+
this.iterateArrayPrototype = newOptions.iterateArrayPrototype || false;
|
|
444
|
+
|
|
445
|
+
// This must be called after options are set
|
|
446
|
+
this.alterDefaultHandlers(newOptions);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Rather than use the strategy design pattern, we'll override our prototype
|
|
451
|
+
* selectively.
|
|
452
|
+
* @param {SAJJOptions} options
|
|
453
|
+
* @returns {void}
|
|
454
|
+
*/
|
|
455
|
+
alterDefaultHandlers (options) {
|
|
456
|
+
if (this.distinguishKeysValues) {
|
|
457
|
+
this.keyValueHandler = this.keyValueDistinguishedHandler;
|
|
458
|
+
}
|
|
459
|
+
if (options.delegateHandlers) {
|
|
460
|
+
this.delegateHandlers = options.delegateHandlers;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// PUBLIC METHODS TO INITIATE PARSING
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* For strings, one may wish to use Clarinet (<https://github.com/dscape/clarinet>) to
|
|
468
|
+
* avoid extra overhead or parsing twice.
|
|
469
|
+
* @param {string} str The JSON string to be walked (after complete conversion
|
|
470
|
+
* to an object)
|
|
471
|
+
* @param {object|object[]} [parentObject] The parent object or array
|
|
472
|
+
* containing the string
|
|
473
|
+
* @param {string} [parentKey] The parent object or array's key
|
|
474
|
+
* @param {boolean} [parentObjectArrayBool] Whether the parent object is an
|
|
475
|
+
* array (not another object)
|
|
476
|
+
* @returns {AnyValue}
|
|
477
|
+
*/
|
|
478
|
+
walkJSONString (str, parentObject, parentKey, parentObjectArrayBool) {
|
|
479
|
+
return this.walkJSONObject(
|
|
480
|
+
JSON.parse(str), parentObject, parentKey, parentObjectArrayBool
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
*
|
|
486
|
+
* @param {import('../jhtml.js').JSONObject} obj The JSON object to walk
|
|
487
|
+
* @param {object|object[]} [parentObject] The parent object or array
|
|
488
|
+
* containing the string
|
|
489
|
+
* @param {string} [parentKey] The parent object or array's key
|
|
490
|
+
* @param {boolean} [parentObjectArrayBool] Whether the parent object is an
|
|
491
|
+
* array (not another object)
|
|
492
|
+
* @property {string|AnyValue} ret The intermediate return value (if any) from
|
|
493
|
+
* beginHandler and delegateHandlersByType delegation
|
|
494
|
+
* @returns {string} The final return value including beginHandler and
|
|
495
|
+
* delegateHandlersByType delegation plus any endHandler additions;
|
|
496
|
+
* one may build one's own intermediate values, but "ret" should be
|
|
497
|
+
* set to return the value
|
|
498
|
+
*/
|
|
499
|
+
walkJSONObject (obj, parentObject, parentKey, parentObjectArrayBool) {
|
|
500
|
+
this.root = obj;
|
|
501
|
+
const parObj = parentObject || this.options.parentObject,
|
|
502
|
+
parKey = parentKey || this.options.parentKey,
|
|
503
|
+
parObjArrBool = parentObjectArrayBool ||
|
|
504
|
+
this.options.parentObjectArrayBool ||
|
|
505
|
+
(parObj && this.isArrayType(parObj));
|
|
506
|
+
this.ret = this.beginHandler(obj, parObj, parKey, parObjArrBool);
|
|
507
|
+
this.ret += this.delegateHandlersByType(obj, parObj, parKey, parObjArrBool);
|
|
508
|
+
this.ret += this.endHandler(obj, parObj, parKey, parObjArrBool);
|
|
509
|
+
return this.ret;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// BEGIN AND END HANDLERS
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* @param {AnyValue} value
|
|
516
|
+
* @param {object|undefined} parentObject
|
|
517
|
+
* @param {string|undefined} parentKey
|
|
518
|
+
* @param {boolean} [parentObjectArrayBool]
|
|
519
|
+
* @returns {string}
|
|
520
|
+
*/
|
|
521
|
+
beginHandler (
|
|
522
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
523
|
+
value, parentObject, parentKey, parentObjectArrayBool
|
|
524
|
+
) {
|
|
525
|
+
return '';
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* We just make available the passed in arguments.
|
|
530
|
+
* @param {AnyValue} obj
|
|
531
|
+
* @param {AnyValue} parObj
|
|
532
|
+
* @param {string|undefined} parKey
|
|
533
|
+
* @param {boolean} [parObjArrBool]
|
|
534
|
+
* @returns {string}
|
|
535
|
+
*/
|
|
536
|
+
endHandler (
|
|
537
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
538
|
+
obj, parObj, parKey, parObjArrBool
|
|
539
|
+
) {
|
|
540
|
+
return '';
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// HANDLER DELEGATION BY TYPE
|
|
544
|
+
|
|
545
|
+
// Todo: override this (or separate out and override secondary method)
|
|
546
|
+
// to delegate objects/arrays separately but for others, pass type
|
|
547
|
+
// as arg, not within method name
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* @param {import('../jhtml.js').JSONObject} obj
|
|
551
|
+
* @param {object|undefined} parentObject
|
|
552
|
+
* @param {string|undefined} parentKey
|
|
553
|
+
* @param {boolean} [parentObjectArrayBool]
|
|
554
|
+
* @returns {string}
|
|
555
|
+
*/
|
|
556
|
+
delegateHandlersByType (obj, parentObject, parentKey, parentObjectArrayBool) {
|
|
557
|
+
const suffix = 'Handler',
|
|
558
|
+
type = this.detectBasicType(
|
|
559
|
+
obj, parentObject, parentKey, parentObjectArrayBool
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
switch (type) {
|
|
563
|
+
case 'null': case 'undefined':
|
|
564
|
+
case 'array': case 'object':
|
|
565
|
+
case 'ignore': // Will delegate by default so that handler can log, etc.
|
|
566
|
+
// Fallthrough
|
|
567
|
+
default:
|
|
568
|
+
return this.delegateHandlers(
|
|
569
|
+
/**
|
|
570
|
+
* @type {`${SAJJType}Handler`}
|
|
571
|
+
*/
|
|
572
|
+
(type + suffix), parentObject, parentKey, parentObjectArrayBool, obj
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* @callback DelegateHandlers
|
|
579
|
+
* Allows override to allow for immediate or delayed execution; should handle
|
|
580
|
+
* both null/undefined types (which require no first value argument since
|
|
581
|
+
* only one is possible) and other types.
|
|
582
|
+
* @param {`${SAJJType}Handler`} type
|
|
583
|
+
* @param {object|undefined} parentObj
|
|
584
|
+
* @param {string|undefined} parentKey
|
|
585
|
+
* @param {boolean} [parentObjectArrayBool]
|
|
586
|
+
* @param {AnyValue} [obj]
|
|
587
|
+
* @returns {string}
|
|
588
|
+
*/
|
|
589
|
+
|
|
590
|
+
/** @type {DelegateHandlers} */
|
|
591
|
+
delegateHandlers (type, parentObj, parentKey, parentObjectArrayBool, obj) {
|
|
592
|
+
return this[type](
|
|
593
|
+
// @ts-ignore Ok
|
|
594
|
+
obj,
|
|
595
|
+
parentObj, parentKey, parentObjectArrayBool
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// DETECT TYPES
|
|
600
|
+
/**
|
|
601
|
+
* @param {AnyValue} obj
|
|
602
|
+
* @param {object|undefined} parentObject
|
|
603
|
+
* @param {string|undefined} parentKey
|
|
604
|
+
* @param {boolean} [parentObjectArrayBool]
|
|
605
|
+
* @returns {SAJJType}
|
|
606
|
+
*/
|
|
607
|
+
detectBasicType (obj, parentObject, parentKey, parentObjectArrayBool) {
|
|
608
|
+
const type = typeof obj,
|
|
609
|
+
JSMode = this.mode === 'JavaScript';
|
|
610
|
+
switch (type) {
|
|
611
|
+
// JavaScript-only
|
|
612
|
+
case 'symbol':
|
|
613
|
+
if (JSMode) {
|
|
614
|
+
return type;
|
|
615
|
+
}
|
|
616
|
+
return this.typeErrorHandler(
|
|
617
|
+
'symbol', obj, parentObject, parentKey, parentObjectArrayBool
|
|
618
|
+
);
|
|
619
|
+
case 'bigint':
|
|
620
|
+
if (JSMode) {
|
|
621
|
+
return type;
|
|
622
|
+
}
|
|
623
|
+
return this.typeErrorHandler(
|
|
624
|
+
'bigint', obj, parentObject, parentKey, parentObjectArrayBool
|
|
625
|
+
);
|
|
626
|
+
case 'number':
|
|
627
|
+
if (!Number.isFinite(obj)) {
|
|
628
|
+
if (JSMode) {
|
|
629
|
+
return 'nonfiniteNumber';
|
|
630
|
+
// Can return a custom type and add that handler to the object to
|
|
631
|
+
// convert to JSON
|
|
632
|
+
}
|
|
633
|
+
return this.typeErrorHandler(
|
|
634
|
+
'nonfiniteNumber', obj, parentObject, parentKey, parentObjectArrayBool
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
return type;
|
|
638
|
+
case 'function': case 'undefined':
|
|
639
|
+
if (!JSMode) {
|
|
640
|
+
// Can return a custom type and add that handler to the object to
|
|
641
|
+
// convert to JSON
|
|
642
|
+
return this.typeErrorHandler(
|
|
643
|
+
type, obj, parentObject, parentKey, parentObjectArrayBool
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
// Fallthrough
|
|
647
|
+
case 'boolean': case 'string':
|
|
648
|
+
return type;
|
|
649
|
+
case 'object':
|
|
650
|
+
return obj
|
|
651
|
+
? (this.isArrayType(obj)
|
|
652
|
+
? 'array'
|
|
653
|
+
: (JSMode ? this.detectObjectType(obj) : 'object')
|
|
654
|
+
)
|
|
655
|
+
: 'null';
|
|
656
|
+
/* c8 ignore next 4 -- Should not reach here */
|
|
657
|
+
default:
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
throw new Error('Unexpected type');
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Could override to always return false if one wished to merge
|
|
665
|
+
* arrayHandler/objectHandler or, if in JSMode, to merge detectObjectType
|
|
666
|
+
* and this isArrayType method. To merge arrayKeyValueHandler and
|
|
667
|
+
* objectKeyValueHandler, see keyValueHandler.
|
|
668
|
+
* @param {AnyValue} obj
|
|
669
|
+
* @returns {boolean}
|
|
670
|
+
*/
|
|
671
|
+
isArrayType (obj) {
|
|
672
|
+
return Object.prototype.toString.call(obj) === '[object Array]';
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Allow overriding to detect `Date`, `RegExp`, or other types (which will in
|
|
677
|
+
* turn route to corresponding names).
|
|
678
|
+
* @param {AnyValue} obj
|
|
679
|
+
* @returns {"object"}
|
|
680
|
+
*/
|
|
681
|
+
detectObjectType (
|
|
682
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
683
|
+
obj
|
|
684
|
+
) {
|
|
685
|
+
return 'object';
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// ERROR HANDLING
|
|
689
|
+
/**
|
|
690
|
+
* May throw or return type string (can be custom type if handler present).
|
|
691
|
+
* @param {SAJJType} type
|
|
692
|
+
* @param {AnyValue} obj
|
|
693
|
+
* @param {object|undefined} parentObject
|
|
694
|
+
* @param {string|undefined} parentKey
|
|
695
|
+
* @param {boolean} [parentObjectArrayBool]
|
|
696
|
+
* @returns {"null"}
|
|
697
|
+
*/
|
|
698
|
+
typeErrorHandler (
|
|
699
|
+
type,
|
|
700
|
+
// eslint-disable-next-line no-unused-vars -- Signature
|
|
701
|
+
obj, parentObject, parentKey, parentObjectArrayBool
|
|
702
|
+
) {
|
|
703
|
+
switch (type) {
|
|
704
|
+
// Could utilize commented out portions as below to allow JSON mode to
|
|
705
|
+
// still handle certain non-JSON types (though may be better to use JS
|
|
706
|
+
// mode in such a case)
|
|
707
|
+
/*
|
|
708
|
+
case 'function':
|
|
709
|
+
return 'ignore';
|
|
710
|
+
// return type;
|
|
711
|
+
case 'undefined':
|
|
712
|
+
return 'ignore';
|
|
713
|
+
// return type;
|
|
714
|
+
// Or maybe this:
|
|
715
|
+
// return 'null';
|
|
716
|
+
*/
|
|
717
|
+
case 'nonfiniteNumber': // We'll behave by default as does JSON.stringify
|
|
718
|
+
return 'null';
|
|
719
|
+
default:
|
|
720
|
+
throw new Error(
|
|
721
|
+
'Values of type "' + type +
|
|
722
|
+
'" are only allowed in JavaScript mode, not JSON.'
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// SPECIALIZED CONSTRUCTORS (JS)
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* @class
|
|
732
|
+
* @param {SAJJOptions} options
|
|
733
|
+
*/
|
|
734
|
+
function SAJJ_JS (options) {
|
|
735
|
+
// We don't make a deep copy, as we only need to overwrite the mode
|
|
736
|
+
const newOpts = /** @type {SAJJOptions} */ (_copyObject(
|
|
737
|
+
// @ts-expect-error Ok
|
|
738
|
+
options
|
|
739
|
+
));
|
|
740
|
+
newOpts.mode = 'JavaScript';
|
|
741
|
+
return new SAJJ(newOpts);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
export {SAJJ_JS};
|
|
745
|
+
|
|
746
|
+
export default SAJJ;
|