@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.
- package/.vscode/launch.json +16 -0
- package/.vscode/settings.json +32 -0
- package/config/api-extractor.json +343 -0
- package/config/rig.json +16 -0
- package/dist/ts-json.d.ts +719 -28
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +25 -0
- package/lib/packlets/diff/detailedDiff.d.ts +375 -0
- package/lib/packlets/diff/detailedDiff.js +342 -0
- package/lib/packlets/diff/index.d.ts +3 -0
- package/lib/packlets/diff/index.js +40 -0
- package/lib/packlets/diff/threeWayDiff.d.ts +263 -0
- package/lib/packlets/diff/threeWayDiff.js +261 -0
- package/lib/packlets/diff/utils.d.ts +6 -0
- package/lib/packlets/diff/utils.js +62 -0
- package/lib/packlets/editor/common.d.ts +6 -0
- package/lib/packlets/editor/jsonEditor.d.ts +57 -28
- package/lib/packlets/editor/jsonEditor.js +101 -32
- package/lib/test/legacy/jsonConverter.conditional.test.d.ts +2 -0
- package/lib/test/legacy/jsonEditor/rules/conditional.test.d.ts +2 -0
- package/lib/test/legacy/jsonEditor/rules/multivalue.test.d.ts +2 -0
- package/lib/test/legacy/jsonEditor/rules/references.test.d.ts +2 -0
- package/lib/test/legacy/jsonEditor/rules/templates.test.d.ts +2 -0
- package/lib/test/unit/contextHelpers.test.d.ts +2 -0
- package/lib/test/unit/converters.test.d.ts +2 -0
- package/lib/test/unit/diff/jsonDiff.test.d.ts +2 -0
- package/lib/test/unit/jsonConverter.test.d.ts +2 -0
- package/lib/test/unit/jsonEditor/jsonEditor.test.d.ts +2 -0
- package/lib/test/unit/jsonEditor/templateContext.test.d.ts +2 -0
- package/lib/test/unit/jsonReferenceMap.test.d.ts +2 -0
- package/package.json +11 -11
- package/src/index.ts +29 -0
- package/src/packlets/context/compositeJsonMap.ts +120 -0
- package/src/packlets/context/contextHelpers.ts +221 -0
- package/src/packlets/context/index.ts +33 -0
- package/src/packlets/context/jsonContext.ts +133 -0
- package/src/packlets/converters/converters.ts +117 -0
- package/src/packlets/converters/index.ts +26 -0
- package/src/packlets/converters/jsonConverter.ts +476 -0
- package/src/packlets/diff/detailedDiff.ts +585 -0
- package/src/packlets/diff/index.ts +24 -0
- package/src/packlets/diff/threeWayDiff.ts +420 -0
- package/src/packlets/diff/utils.ts +66 -0
- package/src/packlets/editor/common.ts +125 -0
- package/src/packlets/editor/index.ts +36 -0
- package/src/packlets/editor/jsonEditor.ts +523 -0
- package/src/packlets/editor/jsonEditorRule.ts +117 -0
- package/src/packlets/editor/jsonEditorState.ts +225 -0
- package/src/packlets/editor/jsonReferenceMap.ts +516 -0
- package/src/packlets/editor/rules/conditional.ts +222 -0
- package/src/packlets/editor/rules/index.ts +25 -0
- package/src/packlets/editor/rules/multivalue.ts +206 -0
- package/src/packlets/editor/rules/references.ts +177 -0
- package/src/packlets/editor/rules/templates.ts +159 -0
- package/CHANGELOG.md +0 -115
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/packlets/context/compositeJsonMap.d.ts.map +0 -1
- package/lib/packlets/context/compositeJsonMap.js.map +0 -1
- package/lib/packlets/context/contextHelpers.d.ts.map +0 -1
- package/lib/packlets/context/contextHelpers.js.map +0 -1
- package/lib/packlets/context/index.d.ts.map +0 -1
- package/lib/packlets/context/index.js.map +0 -1
- package/lib/packlets/context/jsonContext.d.ts.map +0 -1
- package/lib/packlets/context/jsonContext.js.map +0 -1
- package/lib/packlets/converters/converters.d.ts.map +0 -1
- package/lib/packlets/converters/converters.js.map +0 -1
- package/lib/packlets/converters/index.d.ts.map +0 -1
- package/lib/packlets/converters/index.js.map +0 -1
- package/lib/packlets/converters/jsonConverter.d.ts.map +0 -1
- package/lib/packlets/converters/jsonConverter.js.map +0 -1
- package/lib/packlets/editor/common.d.ts.map +0 -1
- package/lib/packlets/editor/common.js.map +0 -1
- package/lib/packlets/editor/index.d.ts.map +0 -1
- package/lib/packlets/editor/index.js.map +0 -1
- package/lib/packlets/editor/jsonEditor.d.ts.map +0 -1
- package/lib/packlets/editor/jsonEditor.js.map +0 -1
- package/lib/packlets/editor/jsonEditorRule.d.ts.map +0 -1
- package/lib/packlets/editor/jsonEditorRule.js.map +0 -1
- package/lib/packlets/editor/jsonEditorState.d.ts.map +0 -1
- package/lib/packlets/editor/jsonEditorState.js.map +0 -1
- package/lib/packlets/editor/jsonReferenceMap.d.ts.map +0 -1
- package/lib/packlets/editor/jsonReferenceMap.js.map +0 -1
- package/lib/packlets/editor/rules/conditional.d.ts.map +0 -1
- package/lib/packlets/editor/rules/conditional.js.map +0 -1
- package/lib/packlets/editor/rules/index.d.ts.map +0 -1
- package/lib/packlets/editor/rules/index.js.map +0 -1
- package/lib/packlets/editor/rules/multivalue.d.ts.map +0 -1
- package/lib/packlets/editor/rules/multivalue.js.map +0 -1
- package/lib/packlets/editor/rules/references.d.ts.map +0 -1
- package/lib/packlets/editor/rules/references.js.map +0 -1
- package/lib/packlets/editor/rules/templates.d.ts.map +0 -1
- 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.
|
|
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
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* @param
|
|
189
|
-
* @
|
|
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
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* @
|
|
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
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
* @param
|
|
248
|
-
* @
|
|
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 = (
|
|
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
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* @param
|
|
296
|
-
* @
|
|
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
|
-
*
|
|
311
|
-
*
|
|
312
|
-
* @
|
|
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
|
-
*
|
|
327
|
-
* @param
|
|
328
|
-
* @
|
|
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) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fgv/ts-json",
|
|
3
|
-
"version": "5.0.0-
|
|
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.
|
|
32
|
+
"ts-jest": "^29.4.1",
|
|
33
33
|
"ts-node": "^10.9.2",
|
|
34
|
-
"typescript": "^5.
|
|
34
|
+
"typescript": "^5.8.3",
|
|
35
35
|
"eslint-plugin-n": "^16.6.2",
|
|
36
|
-
"@rushstack/heft-node-rig": "~2.9.
|
|
37
|
-
"@rushstack/heft": "~0.74.
|
|
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.
|
|
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-
|
|
44
|
-
"@fgv/ts-utils-jest": "5.0.0-
|
|
45
|
-
"@fgv/ts-json-base": "5.0.0-
|
|
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-
|
|
50
|
-
"@fgv/ts-
|
|
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
|
+
}
|