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.
Files changed (70) hide show
  1. package/CHANGES.md +27 -0
  2. package/README.md +61 -164
  3. package/demo/calltemplate-params-demo.js +138 -0
  4. package/demo/codemirror.esm.js +28242 -0
  5. package/demo/codemirror.js +94 -0
  6. package/demo/index.css +7 -0
  7. package/demo/index.html +28 -0
  8. package/demo/index.js +210 -0
  9. package/demo/vendor/jamilih/dist/jml.mjs +2341 -0
  10. package/demo/vendor/jhtml/src/SAJJ/SAJJ.ObjectArrayDelegator.js +356 -0
  11. package/demo/vendor/jhtml/src/SAJJ/SAJJ.Stringifier.js +186 -0
  12. package/demo/vendor/jhtml/src/SAJJ/SAJJ.js +746 -0
  13. package/demo/vendor/jhtml/src/SAJJ/testing/SAJJ.html +33 -0
  14. package/demo/vendor/jhtml/src/SAJJ/testing/SAJJ.testing.js +25 -0
  15. package/demo/vendor/jhtml/src/jhtml-browser.js +5 -0
  16. package/demo/vendor/jhtml/src/jhtml-node.cts +3 -0
  17. package/demo/vendor/jhtml/src/jhtml-node.js +8 -0
  18. package/demo/vendor/jhtml/src/jhtml-node.mts +1 -0
  19. package/demo/vendor/jhtml/src/jhtml.cts +3 -0
  20. package/demo/vendor/jhtml/src/jhtml.js +602 -0
  21. package/demo/vendor/jhtml/src/jhtml.mts +1 -0
  22. package/demo/vendor/jsonpath-plus/dist/index-browser-esm.js +2158 -0
  23. package/demo/vendor/simple-get-json/dist/index-es.js +151 -0
  24. package/demo/xpath2-placeholder.js +1 -0
  25. package/dist/AbstractJoiningTransformer.d.ts +83 -9
  26. package/dist/AbstractJoiningTransformer.d.ts.map +1 -1
  27. package/dist/DOMJoiningTransformer.d.ts +85 -25
  28. package/dist/DOMJoiningTransformer.d.ts.map +1 -1
  29. package/dist/JSONJoiningTransformer.d.ts +159 -51
  30. package/dist/JSONJoiningTransformer.d.ts.map +1 -1
  31. package/dist/JSONPathTransformer.d.ts +37 -38
  32. package/dist/JSONPathTransformer.d.ts.map +1 -1
  33. package/dist/JSONPathTransformerContext.d.ts +334 -124
  34. package/dist/JSONPathTransformerContext.d.ts.map +1 -1
  35. package/dist/StringJoiningTransformer.d.ts +132 -41
  36. package/dist/StringJoiningTransformer.d.ts.map +1 -1
  37. package/dist/XPathTransformer.d.ts +35 -20
  38. package/dist/XPathTransformer.d.ts.map +1 -1
  39. package/dist/XPathTransformerContext.d.ts +281 -105
  40. package/dist/XPathTransformerContext.d.ts.map +1 -1
  41. package/dist/index-browser.d.ts +4 -0
  42. package/dist/index-browser.d.ts.map +1 -0
  43. package/dist/index-node.d.ts +4 -0
  44. package/dist/index-node.d.ts.map +1 -0
  45. package/dist/index.d.ts +330 -57
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/types.d.ts +204 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/docs/API.expanded.md +172 -5
  50. package/docs/API.md +92 -2
  51. package/docs/TO-DO.md +168 -0
  52. package/docs/calltemplate-params.md +251 -0
  53. package/eslint.config.js +11 -5
  54. package/package.json +35 -9
  55. package/pnpm-workspace.yaml +1 -0
  56. package/rollup.config.js +13 -0
  57. package/src/AbstractJoiningTransformer.js +54 -15
  58. package/src/DOMJoiningTransformer.js +275 -28
  59. package/src/JSONJoiningTransformer.js +351 -70
  60. package/src/JSONPathTransformer.js +48 -30
  61. package/src/JSONPathTransformerContext.js +729 -107
  62. package/src/StringJoiningTransformer.js +311 -57
  63. package/src/XPathTransformer.js +27 -12
  64. package/src/XPathTransformerContext.js +928 -99
  65. package/src/index-browser.js +5 -0
  66. package/src/index-node.js +7 -0
  67. package/src/index.js +502 -98
  68. package/tsconfig.json +5 -2
  69. package/typings/xpath2-js.d.ts +40 -1
  70. 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;