@fgv/ts-json 5.0.0-2 → 5.0.0-21

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 (94) hide show
  1. package/.vscode/launch.json +16 -0
  2. package/.vscode/settings.json +32 -0
  3. package/config/api-extractor.json +343 -0
  4. package/config/rig.json +16 -0
  5. package/dist/ts-json.d.ts +719 -28
  6. package/dist/tsdoc-metadata.json +1 -1
  7. package/lib/index.d.ts +2 -0
  8. package/lib/index.js +25 -0
  9. package/lib/packlets/diff/detailedDiff.d.ts +375 -0
  10. package/lib/packlets/diff/detailedDiff.js +342 -0
  11. package/lib/packlets/diff/index.d.ts +3 -0
  12. package/lib/packlets/diff/index.js +40 -0
  13. package/lib/packlets/diff/threeWayDiff.d.ts +263 -0
  14. package/lib/packlets/diff/threeWayDiff.js +261 -0
  15. package/lib/packlets/diff/utils.d.ts +6 -0
  16. package/lib/packlets/diff/utils.js +62 -0
  17. package/lib/packlets/editor/common.d.ts +6 -0
  18. package/lib/packlets/editor/jsonEditor.d.ts +57 -28
  19. package/lib/packlets/editor/jsonEditor.js +101 -32
  20. package/lib/test/legacy/jsonConverter.conditional.test.d.ts +2 -0
  21. package/lib/test/legacy/jsonEditor/rules/conditional.test.d.ts +2 -0
  22. package/lib/test/legacy/jsonEditor/rules/multivalue.test.d.ts +2 -0
  23. package/lib/test/legacy/jsonEditor/rules/references.test.d.ts +2 -0
  24. package/lib/test/legacy/jsonEditor/rules/templates.test.d.ts +2 -0
  25. package/lib/test/unit/contextHelpers.test.d.ts +2 -0
  26. package/lib/test/unit/converters.test.d.ts +2 -0
  27. package/lib/test/unit/diff/jsonDiff.test.d.ts +2 -0
  28. package/lib/test/unit/jsonConverter.test.d.ts +2 -0
  29. package/lib/test/unit/jsonEditor/jsonEditor.test.d.ts +2 -0
  30. package/lib/test/unit/jsonEditor/templateContext.test.d.ts +2 -0
  31. package/lib/test/unit/jsonReferenceMap.test.d.ts +2 -0
  32. package/package.json +11 -11
  33. package/src/index.ts +29 -0
  34. package/src/packlets/context/compositeJsonMap.ts +120 -0
  35. package/src/packlets/context/contextHelpers.ts +221 -0
  36. package/src/packlets/context/index.ts +33 -0
  37. package/src/packlets/context/jsonContext.ts +133 -0
  38. package/src/packlets/converters/converters.ts +117 -0
  39. package/src/packlets/converters/index.ts +26 -0
  40. package/src/packlets/converters/jsonConverter.ts +476 -0
  41. package/src/packlets/diff/detailedDiff.ts +585 -0
  42. package/src/packlets/diff/index.ts +24 -0
  43. package/src/packlets/diff/threeWayDiff.ts +420 -0
  44. package/src/packlets/diff/utils.ts +66 -0
  45. package/src/packlets/editor/common.ts +125 -0
  46. package/src/packlets/editor/index.ts +36 -0
  47. package/src/packlets/editor/jsonEditor.ts +523 -0
  48. package/src/packlets/editor/jsonEditorRule.ts +117 -0
  49. package/src/packlets/editor/jsonEditorState.ts +225 -0
  50. package/src/packlets/editor/jsonReferenceMap.ts +516 -0
  51. package/src/packlets/editor/rules/conditional.ts +222 -0
  52. package/src/packlets/editor/rules/index.ts +25 -0
  53. package/src/packlets/editor/rules/multivalue.ts +206 -0
  54. package/src/packlets/editor/rules/references.ts +177 -0
  55. package/src/packlets/editor/rules/templates.ts +159 -0
  56. package/CHANGELOG.md +0 -115
  57. package/lib/index.d.ts.map +0 -1
  58. package/lib/index.js.map +0 -1
  59. package/lib/packlets/context/compositeJsonMap.d.ts.map +0 -1
  60. package/lib/packlets/context/compositeJsonMap.js.map +0 -1
  61. package/lib/packlets/context/contextHelpers.d.ts.map +0 -1
  62. package/lib/packlets/context/contextHelpers.js.map +0 -1
  63. package/lib/packlets/context/index.d.ts.map +0 -1
  64. package/lib/packlets/context/index.js.map +0 -1
  65. package/lib/packlets/context/jsonContext.d.ts.map +0 -1
  66. package/lib/packlets/context/jsonContext.js.map +0 -1
  67. package/lib/packlets/converters/converters.d.ts.map +0 -1
  68. package/lib/packlets/converters/converters.js.map +0 -1
  69. package/lib/packlets/converters/index.d.ts.map +0 -1
  70. package/lib/packlets/converters/index.js.map +0 -1
  71. package/lib/packlets/converters/jsonConverter.d.ts.map +0 -1
  72. package/lib/packlets/converters/jsonConverter.js.map +0 -1
  73. package/lib/packlets/editor/common.d.ts.map +0 -1
  74. package/lib/packlets/editor/common.js.map +0 -1
  75. package/lib/packlets/editor/index.d.ts.map +0 -1
  76. package/lib/packlets/editor/index.js.map +0 -1
  77. package/lib/packlets/editor/jsonEditor.d.ts.map +0 -1
  78. package/lib/packlets/editor/jsonEditor.js.map +0 -1
  79. package/lib/packlets/editor/jsonEditorRule.d.ts.map +0 -1
  80. package/lib/packlets/editor/jsonEditorRule.js.map +0 -1
  81. package/lib/packlets/editor/jsonEditorState.d.ts.map +0 -1
  82. package/lib/packlets/editor/jsonEditorState.js.map +0 -1
  83. package/lib/packlets/editor/jsonReferenceMap.d.ts.map +0 -1
  84. package/lib/packlets/editor/jsonReferenceMap.js.map +0 -1
  85. package/lib/packlets/editor/rules/conditional.d.ts.map +0 -1
  86. package/lib/packlets/editor/rules/conditional.js.map +0 -1
  87. package/lib/packlets/editor/rules/index.d.ts.map +0 -1
  88. package/lib/packlets/editor/rules/index.js.map +0 -1
  89. package/lib/packlets/editor/rules/multivalue.d.ts.map +0 -1
  90. package/lib/packlets/editor/rules/multivalue.js.map +0 -1
  91. package/lib/packlets/editor/rules/references.d.ts.map +0 -1
  92. package/lib/packlets/editor/rules/references.js.map +0 -1
  93. package/lib/packlets/editor/rules/templates.d.ts.map +0 -1
  94. package/lib/packlets/editor/rules/templates.js.map +0 -1
@@ -84,9 +84,15 @@ class JsonEditor {
84
84
  ]);
85
85
  }
86
86
  /**
87
+ * Creates a complete IJsonEditorOptions object from partial options, filling in
88
+ * default values for any missing properties. This ensures all editor instances
89
+ * have consistent, complete configuration including validation rules and merge behavior.
90
+ * @param options - Optional partial editor options to merge with defaults
91
+ * @returns Success with complete editor options, or Failure if validation fails
87
92
  * @internal
88
93
  */
89
94
  static _getDefaultOptions(options) {
95
+ var _a;
90
96
  const context = options === null || options === void 0 ? void 0 : options.context;
91
97
  let validation = options === null || options === void 0 ? void 0 : options.validation;
92
98
  if (validation === undefined) {
@@ -99,7 +105,14 @@ class JsonEditor {
99
105
  let merge = options === null || options === void 0 ? void 0 : options.merge;
100
106
  if (merge === undefined) {
101
107
  merge = {
102
- arrayMergeBehavior: 'append'
108
+ arrayMergeBehavior: 'append',
109
+ nullAsDelete: false
110
+ };
111
+ }
112
+ else {
113
+ merge = {
114
+ arrayMergeBehavior: merge.arrayMergeBehavior,
115
+ nullAsDelete: (_a = merge.nullAsDelete) !== null && _a !== void 0 ? _a : false
103
116
  };
104
117
  }
105
118
  return (0, ts_utils_1.succeed)({ context, validation, merge });
@@ -171,7 +184,7 @@ class JsonEditor {
171
184
  return (0, ts_utils_1.succeedWithDetail)(value, 'edited');
172
185
  }
173
186
  else if ((0, ts_json_base_1.isJsonObject)(value)) {
174
- return this.mergeObjectInPlace({}, value, state.context).withFailureDetail('error');
187
+ return this._cloneObjectWithoutNullAsDelete({}, value, state);
175
188
  }
176
189
  else if ((0, ts_json_base_1.isJsonArray)(value)) {
177
190
  return this._cloneArray(value, state.context);
@@ -182,16 +195,22 @@ class JsonEditor {
182
195
  return state.failValidation('invalidPropertyValue', `Cannot convert invalid JSON: "${JSON.stringify(value)}"`);
183
196
  }
184
197
  /**
185
- *
186
- * @param target -
187
- * @param src -
188
- * @param state -
189
- * @returns
198
+ * Merges properties from a source object into a target object, applying editor rules and
199
+ * null-as-delete logic. This is the core merge implementation that handles property-by-property
200
+ * merging with rule processing and deferred property handling.
201
+ * @param target - The target object to merge properties into
202
+ * @param src - The source object containing properties to merge
203
+ * @param state - The editor state containing options and context
204
+ * @returns Success with the modified target object, or Failure with error details
190
205
  * @internal
191
206
  */
192
207
  _mergeObjectInPlace(target, src, state) {
193
208
  for (const key in src) {
194
209
  if (src.hasOwnProperty(key)) {
210
+ // Skip dangerous property names to prevent prototype pollution
211
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
212
+ continue;
213
+ }
195
214
  const propResult = this._editProperty(key, src[key], state);
196
215
  if (propResult.isSuccess()) {
197
216
  if (propResult.detail === 'deferred') {
@@ -223,10 +242,12 @@ class JsonEditor {
223
242
  return (0, ts_utils_1.succeed)(target);
224
243
  }
225
244
  /**
226
- *
227
- * @param src -
228
- * @param context -
229
- * @returns
245
+ * Creates a deep clone of a JSON array by recursively cloning each element.
246
+ * Each array element is cloned using the main clone method, preserving the
247
+ * editor's rules and validation settings.
248
+ * @param src - The source JSON array to clone
249
+ * @param context - Optional JSON context for cloning operations
250
+ * @returns Success with the cloned array, or Failure with error details
230
251
  * @internal
231
252
  */
232
253
  _cloneArray(src, context) {
@@ -240,17 +261,30 @@ class JsonEditor {
240
261
  .withFailureDetail('error');
241
262
  }
242
263
  /**
243
- *
244
- * @param target -
245
- * @param key -
246
- * @param newValue -
247
- * @param state -
248
- * @returns
264
+ * Merges a single cloned property value into a target object. This method handles
265
+ * the core merge logic including null-as-delete behavior, array merging, and
266
+ * recursive object merging. The null-as-delete check occurs before primitive
267
+ * handling to ensure null values can signal property deletion.
268
+ * @param target - The target object to merge the property into
269
+ * @param key - The property key being merged
270
+ * @param newValue - The cloned value to merge (from source object)
271
+ * @param state - The editor state containing merge options and context
272
+ * @returns Success with the merged value, or Failure with error details
249
273
  * @internal
250
274
  */
251
275
  _mergeClonedProperty(target, key, newValue, state) {
252
- var _a, _b;
276
+ var _a, _b, _c;
277
+ // Skip dangerous property names to prevent prototype pollution
278
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
279
+ return (0, ts_utils_1.succeedWithDetail)(newValue, 'edited');
280
+ }
253
281
  const existing = target[key];
282
+ // Handle null-as-delete behavior before primitive check
283
+ /* c8 ignore next 1 - ? is defense in depth */
284
+ if (newValue === null && ((_a = state.options.merge) === null || _a === void 0 ? void 0 : _a.nullAsDelete) === true) {
285
+ delete target[key];
286
+ return (0, ts_utils_1.succeedWithDetail)(null, 'edited');
287
+ }
254
288
  // merge is called right after clone so this should never happen
255
289
  // since clone itself will have failed
256
290
  if ((0, ts_json_base_1.isJsonPrimitive)(newValue)) {
@@ -268,7 +302,7 @@ class JsonEditor {
268
302
  if ((0, ts_json_base_1.isJsonArray)(newValue)) {
269
303
  if ((0, ts_json_base_1.isJsonArray)(existing)) {
270
304
  /* c8 ignore next 1 - ?? is defense in depth */
271
- const arrayMergeBehavior = (_b = (_a = state.options.merge) === null || _a === void 0 ? void 0 : _a.arrayMergeBehavior) !== null && _b !== void 0 ? _b : 'append';
305
+ const arrayMergeBehavior = (_c = (_b = state.options.merge) === null || _b === void 0 ? void 0 : _b.arrayMergeBehavior) !== null && _c !== void 0 ? _c : 'append';
272
306
  switch (arrayMergeBehavior) {
273
307
  case 'append':
274
308
  target[key] = existing.concat(...newValue);
@@ -289,11 +323,13 @@ class JsonEditor {
289
323
  return (0, ts_utils_1.failWithDetail)(`Invalid JSON: ${JSON.stringify(newValue)}`, 'error');
290
324
  } /* c8 ignore stop */
291
325
  /**
292
- *
293
- * @param key -
294
- * @param value -
295
- * @param state -
296
- * @returns
326
+ * Applies editor rules to a single property during merge operations. This method
327
+ * iterates through all configured editor rules to process the property, handling
328
+ * templates, conditionals, multi-value properties, and references.
329
+ * @param key - The property key to edit
330
+ * @param value - The property value to edit
331
+ * @param state - The editor state containing rules and context
332
+ * @returns Success with transformed property object, or Failure if rules cannot process
297
333
  * @internal
298
334
  */
299
335
  _editProperty(key, value, state) {
@@ -306,10 +342,12 @@ class JsonEditor {
306
342
  return (0, ts_utils_1.failWithDetail)('inapplicable', 'inapplicable');
307
343
  }
308
344
  /**
309
- *
310
- * @param value -
311
- * @param state -
312
- * @returns
345
+ * Applies editor rules to a single JSON value during clone operations. This method
346
+ * iterates through all configured editor rules to process the value, handling
347
+ * templates, conditionals, multi-value expressions, and references.
348
+ * @param value - The JSON value to edit and transform
349
+ * @param state - The editor state containing rules and context
350
+ * @returns Success with transformed value, or Failure if rules cannot process
313
351
  * @internal
314
352
  */
315
353
  _editValue(value, state) {
@@ -322,10 +360,41 @@ class JsonEditor {
322
360
  return (0, ts_utils_1.failWithDetail)('inapplicable', 'inapplicable');
323
361
  }
324
362
  /**
325
- *
326
- * @param target -
327
- * @param state -
328
- * @returns
363
+ * Clone an object without applying null-as-delete behavior.
364
+ * This preserves null values during cloning so they can be used for deletion signaling during merge.
365
+ * @param target - The target object to clone into
366
+ * @param src - The source object to clone
367
+ * @param state - The editor state
368
+ * @returns The cloned object
369
+ * @internal
370
+ */
371
+ _cloneObjectWithoutNullAsDelete(target, src, state) {
372
+ var _a, _b;
373
+ // Temporarily disable null-as-delete during cloning
374
+ const modifiedOptions = {
375
+ context: state.options.context,
376
+ validation: state.options.validation,
377
+ merge: {
378
+ /* c8 ignore next 1 - ? is defense in depth */
379
+ arrayMergeBehavior: (_b = (_a = state.options.merge) === null || _a === void 0 ? void 0 : _a.arrayMergeBehavior) !== null && _b !== void 0 ? _b : 'append',
380
+ nullAsDelete: false
381
+ }
382
+ };
383
+ const modifiedState = new jsonEditorState_1.JsonEditorState(state.editor, modifiedOptions, state.context);
384
+ return this._mergeObjectInPlace(target, src, modifiedState)
385
+ .onSuccess((merged) => {
386
+ return this._finalizeAndMerge(merged, modifiedState);
387
+ })
388
+ .withFailureDetail('error');
389
+ }
390
+ /**
391
+ * Finalizes the merge operation by processing any deferred properties and merging
392
+ * them into the target object. Deferred properties are those that require special
393
+ * processing after the initial merge phase, such as references that depend on
394
+ * other properties being resolved first.
395
+ * @param target - The target object that has been merged
396
+ * @param state - The editor state containing deferred properties and rules
397
+ * @returns Success with the finalized target object, or Failure with error details
329
398
  * @internal
330
399
  */
331
400
  _finalizeAndMerge(target, state) {
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=jsonConverter.conditional.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=conditional.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=multivalue.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=references.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=templates.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=contextHelpers.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=converters.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=jsonDiff.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=jsonConverter.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=jsonEditor.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=templateContext.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=jsonReferenceMap.test.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fgv/ts-json",
3
- "version": "5.0.0-2",
3
+ "version": "5.0.0-21",
4
4
  "description": "Typescript utilities for working with JSON",
5
5
  "main": "lib/index.js",
6
6
  "types": "dist/ts-json.d.ts",
@@ -29,25 +29,25 @@
29
29
  "jest-extended": "^4.0.2",
30
30
  "mustache": "^4.2.0",
31
31
  "rimraf": "^5.0.7",
32
- "ts-jest": "^29.4.0",
32
+ "ts-jest": "^29.4.1",
33
33
  "ts-node": "^10.9.2",
34
- "typescript": "^5.7.3",
34
+ "typescript": "^5.8.3",
35
35
  "eslint-plugin-n": "^16.6.2",
36
- "@rushstack/heft-node-rig": "~2.9.0",
37
- "@rushstack/heft": "~0.74.0",
36
+ "@rushstack/heft-node-rig": "~2.9.3",
37
+ "@rushstack/heft": "~0.74.2",
38
38
  "heft-jest": "~1.0.2",
39
39
  "@types/heft-jest": "1.0.6",
40
- "@microsoft/api-documenter": "^7.26.29",
40
+ "@microsoft/api-documenter": "^7.26.31",
41
41
  "@rushstack/eslint-config": "~4.4.0",
42
42
  "eslint-plugin-tsdoc": "~0.4.0",
43
- "@fgv/ts-utils": "5.0.0-2",
44
- "@fgv/ts-utils-jest": "5.0.0-2",
45
- "@fgv/ts-json-base": "5.0.0-2"
43
+ "@fgv/ts-utils": "5.0.0-21",
44
+ "@fgv/ts-utils-jest": "5.0.0-21",
45
+ "@fgv/ts-json-base": "5.0.0-21"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "mustache": "^4.2.0",
49
- "@fgv/ts-json-base": "5.0.0-2",
50
- "@fgv/ts-utils": "5.0.0-2"
49
+ "@fgv/ts-utils": "5.0.0-21",
50
+ "@fgv/ts-json-base": "5.0.0-21"
51
51
  },
52
52
  "scripts": {
53
53
  "build": "heft build --clean",
package/src/index.ts ADDED
@@ -0,0 +1,29 @@
1
+ /*
2
+ * Copyright (c) 2023 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+ export * from './packlets/context';
24
+ export * from './packlets/converters';
25
+ export * from './packlets/editor';
26
+
27
+ import * as Diff from './packlets/diff';
28
+
29
+ export { Diff };
@@ -0,0 +1,120 @@
1
+ /*
2
+ * Copyright (c) 2020 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+ import { JsonObject, JsonValue, isJsonObject } from '@fgv/ts-json-base';
24
+ import { DetailedResult, Result, captureResult, failWithDetail, succeedWithDetail } from '@fgv/ts-utils';
25
+ import { IJsonContext, IJsonReferenceMap, JsonReferenceMapFailureReason } from './jsonContext';
26
+
27
+ /**
28
+ * A {@link CompositeJsonMap | CompositeJsonMap} presents a composed view of one or more other
29
+ * {@link IJsonReferenceMap | JSON reference maps}.
30
+ * @public
31
+ */
32
+ export class CompositeJsonMap implements IJsonReferenceMap {
33
+ /**
34
+ * The {@link IJsonReferenceMap | reference maps} from which this map is composed.
35
+ * @internal
36
+ */
37
+ protected _maps: IJsonReferenceMap[];
38
+
39
+ /**
40
+ * The {@link IJsonReferenceMap | reference maps} from which this map is to be composed.
41
+ * @param maps - An array o {@link IJsonReferenceMap | IJsonReferenceMap} containing the maps
42
+ * from which this map is to be composed.
43
+ * @internal
44
+ */
45
+ protected constructor(maps: IJsonReferenceMap[]) {
46
+ this._maps = maps;
47
+ }
48
+
49
+ /**
50
+ * Creates a new {@link CompositeJsonMap | CompositeJsonMap} from supplied
51
+ * {@link IJsonReferenceMap | maps}.
52
+ * @param maps - one or more {@link IJsonReferenceMap | object maps} to be composed.
53
+ */
54
+ public static create(maps: IJsonReferenceMap[]): Result<CompositeJsonMap> {
55
+ return captureResult(() => new CompositeJsonMap(maps));
56
+ }
57
+
58
+ /**
59
+ * Determine if a key might be valid for this map but does not determine
60
+ * if key actually exists. Allows key range to be constrained.
61
+ * @param key - The key to be tested.
62
+ * @returns `true` if the key is in the valid range, `false` otherwise.
63
+ */
64
+ public keyIsInRange(key: string): boolean {
65
+ return this._maps.find((map) => map.keyIsInRange(key)) !== undefined;
66
+ }
67
+
68
+ /**
69
+ * Determines if an object with the specified key actually exists in the map.
70
+ * @param key - The key to be tested.
71
+ * @returns `true` if an object with the specified key exists, `false` otherwise.
72
+ */
73
+ public has(key: string): boolean {
74
+ return this._maps.find((map) => map.has(key)) !== undefined;
75
+ }
76
+
77
+ /**
78
+ * Gets a JSON object specified by key.
79
+ * @param key - The key of the object to be retrieved.
80
+ * @param context - An optional {@link IJsonContext | JSON Context} used to format the object.
81
+ * @returns `Success` with the formatted object if successful. `Failure` with detail `'unknown'`
82
+ * if no such object exists, or `Failure` with detail `'error'` if the object was found but
83
+ * could not be formatted.
84
+ */
85
+ public getJsonObject(
86
+ key: string,
87
+ context?: IJsonContext
88
+ ): DetailedResult<JsonObject, JsonReferenceMapFailureReason> {
89
+ return this.getJsonValue(key, context).onSuccess((jv) => {
90
+ if (!isJsonObject(jv)) {
91
+ return failWithDetail(`${key}: not an object`, 'error');
92
+ }
93
+ return succeedWithDetail(jv);
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Gets a JSON value specified by key.
99
+ * @param key - The key of the object to be retrieved.
100
+ * @param context - An optional {@link IJsonContext | JSON Context} used to format the value.
101
+ * @returns `Success` with the formatted object if successful. `Failure` with detail `'unknown'`
102
+ * if no such object exists, or failure with detail `'error'` if the object was found but
103
+ * could not be formatted.
104
+ */
105
+ // eslint-disable-next-line no-use-before-define
106
+ public getJsonValue(
107
+ key: string,
108
+ context?: IJsonContext
109
+ ): DetailedResult<JsonValue, JsonReferenceMapFailureReason> {
110
+ for (const map of this._maps) {
111
+ if (map.keyIsInRange(key)) {
112
+ const result = map.getJsonValue(key, context);
113
+ if (result.isSuccess() || result.detail === 'error') {
114
+ return result;
115
+ }
116
+ }
117
+ }
118
+ return failWithDetail(`${key}: value not found`, 'unknown');
119
+ }
120
+ }