UNPKG

@ajsf/core

Version:

Angular JSON Schema Form builder core

1,026 lines 130 kB
import { cleanValueOfQuotes, copy, getExpressionType, getKeyAndValueByExpressionType, hasOwn, isEqual, isNotEqual, isNotExpression } from './utility.functions'; import { Injectable } from '@angular/core'; import { isArray, isDefined, isEmpty, isMap, isNumber, isObject, isString } from './validator.functions'; import * as i0 from "@angular/core"; export class JsonPointer { /** * 'get' function * * Uses a JSON Pointer to retrieve a value from an object. * * // { object } object - Object to get value from * // { Pointer } pointer - JSON Pointer (string or array) * // { number = 0 } startSlice - Zero-based index of first Pointer key to use * // { number } endSlice - Zero-based index of last Pointer key to use * // { boolean = false } getBoolean - Return only true or false? * // { boolean = false } errors - Show error if not found? * // { object } - Located value (or true or false if getBoolean = true) */ static get(object, pointer, startSlice = 0, endSlice = null, getBoolean = false, errors = false) { if (object === null) { return getBoolean ? false : undefined; } let keyArray = this.parse(pointer, errors); if (typeof object === 'object' && keyArray !== null) { let subObject = object; if (startSlice >= keyArray.length || endSlice <= -keyArray.length) { return object; } if (startSlice <= -keyArray.length) { startSlice = 0; } if (!isDefined(endSlice) || endSlice >= keyArray.length) { endSlice = keyArray.length; } keyArray = keyArray.slice(startSlice, endSlice); for (let key of keyArray) { if (key === '-' && isArray(subObject) && subObject.length) { key = subObject.length - 1; } if (isMap(subObject) && subObject.has(key)) { subObject = subObject.get(key); } else if (typeof subObject === 'object' && subObject !== null && hasOwn(subObject, key)) { subObject = subObject[key]; } else { const evaluatedExpression = JsonPointer.evaluateExpression(subObject, key); if (evaluatedExpression.passed) { subObject = evaluatedExpression.key ? subObject[evaluatedExpression.key] : subObject; } else { this.logErrors(errors, key, pointer, object); return getBoolean ? false : undefined; } } } return getBoolean ? true : subObject; } if (errors && keyArray === null) { console.error(`get error: Invalid JSON Pointer: ${pointer}`); } if (errors && typeof object !== 'object') { console.error('get error: Invalid object:'); console.error(object); } return getBoolean ? false : undefined; } static logErrors(errors, key, pointer, object) { if (errors) { console.error(`get error: "${key}" key not found in object.`); console.error(pointer); console.error(object); } } /** * Evaluates conditional expression in form of `model.<property>==<value>` or * `model.<property>!=<value>` where the first one means that the value must match to be * shown in a form, while the former shows the property only when the property value is not * set, or does not equal the given value. * * // { subObject } subObject - an object containing the data values of properties * // { key } key - the key from the for loop in a form of `<property>==<value>` * * Returns the object with two properties. The property passed informs whether * the expression evaluated successfully and the property key returns either the same * key if it is not contained inside the subObject or the key of the property if it is contained. */ static evaluateExpression(subObject, key) { const defaultResult = { passed: false, key: key }; const keysAndExpression = this.parseKeysAndExpression(key, subObject); if (!keysAndExpression) { return defaultResult; } const ownCheckResult = this.doOwnCheckResult(subObject, keysAndExpression); if (ownCheckResult) { return ownCheckResult; } const cleanedValue = cleanValueOfQuotes(keysAndExpression.keyAndValue[1]); const evaluatedResult = this.performExpressionOnValue(keysAndExpression, cleanedValue, subObject); if (evaluatedResult) { return evaluatedResult; } return defaultResult; } /** * Performs the actual evaluation on the given expression with given values and keys. * // { cleanedValue } cleanedValue - the given valued cleaned of quotes if it had any * // { subObject } subObject - the object with properties values * // { keysAndExpression } keysAndExpression - an object holding the expressions with */ static performExpressionOnValue(keysAndExpression, cleanedValue, subObject) { const propertyByKey = subObject[keysAndExpression.keyAndValue[0]]; if (this.doComparisonByExpressionType(keysAndExpression.expressionType, propertyByKey, cleanedValue)) { return { passed: true, key: keysAndExpression.keyAndValue[0] }; } return null; } static doComparisonByExpressionType(expressionType, propertyByKey, cleanedValue) { if (isEqual(expressionType)) { return propertyByKey === cleanedValue; } if (isNotEqual(expressionType)) { return propertyByKey !== cleanedValue; } return false; } /** * Does the checks when the parsed key is actually no a property inside subObject. * That would mean that the equal comparison makes no sense and thus the negative result * is returned, and the not equal comparison is not necessary because it doesn't equal * obviously. Returns null when the given key is a real property inside the subObject. * // { subObject } subObject - the object with properties values * // { keysAndExpression } keysAndExpression - an object holding the expressions with * the associated keys. */ static doOwnCheckResult(subObject, keysAndExpression) { let ownCheckResult = null; if (!hasOwn(subObject, keysAndExpression.keyAndValue[0])) { if (isEqual(keysAndExpression.expressionType)) { ownCheckResult = { passed: false, key: null }; } if (isNotEqual(keysAndExpression.expressionType)) { ownCheckResult = { passed: true, key: null }; } } return ownCheckResult; } /** * Does the basic checks and tries to parse an expression and a pair * of key and value. * // { key } key - the original for loop created value containing key and value in one string * // { subObject } subObject - the object with properties values */ static parseKeysAndExpression(key, subObject) { if (this.keyOrSubObjEmpty(key, subObject)) { return null; } const expressionType = getExpressionType(key.toString()); if (isNotExpression(expressionType)) { return null; } const keyAndValue = getKeyAndValueByExpressionType(expressionType, key); if (!keyAndValue || !keyAndValue[0] || !keyAndValue[1]) { return null; } return { expressionType: expressionType, keyAndValue: keyAndValue }; } static keyOrSubObjEmpty(key, subObject) { return !key || !subObject; } /** * 'getCopy' function * * Uses a JSON Pointer to deeply clone a value from an object. * * // { object } object - Object to get value from * // { Pointer } pointer - JSON Pointer (string or array) * // { number = 0 } startSlice - Zero-based index of first Pointer key to use * // { number } endSlice - Zero-based index of last Pointer key to use * // { boolean = false } getBoolean - Return only true or false? * // { boolean = false } errors - Show error if not found? * // { object } - Located value (or true or false if getBoolean = true) */ static getCopy(object, pointer, startSlice = 0, endSlice = null, getBoolean = false, errors = false) { const objectToCopy = this.get(object, pointer, startSlice, endSlice, getBoolean, errors); return this.forEachDeepCopy(objectToCopy); } /** * 'getFirst' function * * Takes an array of JSON Pointers and objects, * checks each object for a value specified by the pointer, * and returns the first value found. * * // { [object, pointer][] } items - Array of objects and pointers to check * // { any = null } defaultValue - Value to return if nothing found * // { boolean = false } getCopy - Return a copy instead? * // - First value found */ static getFirst(items, defaultValue = null, getCopy = false) { if (isEmpty(items)) { return; } if (isArray(items)) { for (const item of items) { if (isEmpty(item)) { continue; } if (isArray(item) && item.length >= 2) { if (isEmpty(item[0]) || isEmpty(item[1])) { continue; } const value = getCopy ? this.getCopy(item[0], item[1]) : this.get(item[0], item[1]); if (value) { return value; } continue; } console.error('getFirst error: Input not in correct format.\n' + 'Should be: [ [ object1, pointer1 ], [ object 2, pointer2 ], etc... ]'); return; } return defaultValue; } if (isMap(items)) { for (const [object, pointer] of items) { if (object === null || !this.isJsonPointer(pointer)) { continue; } const value = getCopy ? this.getCopy(object, pointer) : this.get(object, pointer); if (value) { return value; } } return defaultValue; } console.error('getFirst error: Input not in correct format.\n' + 'Should be: [ [ object1, pointer1 ], [ object 2, pointer2 ], etc... ]'); return defaultValue; } /** * 'getFirstCopy' function * * Similar to getFirst, but always returns a copy. * * // { [object, pointer][] } items - Array of objects and pointers to check * // { any = null } defaultValue - Value to return if nothing found * // - Copy of first value found */ static getFirstCopy(items, defaultValue = null) { const firstCopy = this.getFirst(items, defaultValue, true); return firstCopy; } /** * 'set' function * * Uses a JSON Pointer to set a value on an object. * Also creates any missing sub objects or arrays to contain that value. * * If the optional fourth parameter is TRUE and the inner-most container * is an array, the function will insert the value as a new item at the * specified location in the array, rather than overwriting the existing * value (if any) at that location. * * So set([1, 2, 3], '/1', 4) => [1, 4, 3] * and * So set([1, 2, 3], '/1', 4, true) => [1, 4, 2, 3] * * // { object } object - The object to set value in * // { Pointer } pointer - The JSON Pointer (string or array) * // value - The new value to set * // { boolean } insert - insert value? * // { object } - The original object, modified with the set value */ static set(object, pointer, value, insert = false) { const keyArray = this.parse(pointer); if (keyArray !== null && keyArray.length) { let subObject = object; for (let i = 0; i < keyArray.length - 1; ++i) { let key = keyArray[i]; if (key === '-' && isArray(subObject)) { key = subObject.length; } if (isMap(subObject) && subObject.has(key)) { subObject = subObject.get(key); } else { if (!hasOwn(subObject, key)) { subObject[key] = (keyArray[i + 1].match(/^(\d+|-)$/)) ? [] : {}; } subObject = subObject[key]; } } const lastKey = keyArray[keyArray.length - 1]; if (isArray(subObject) && lastKey === '-') { subObject.push(value); } else if (insert && isArray(subObject) && !isNaN(+lastKey)) { subObject.splice(lastKey, 0, value); } else if (isMap(subObject)) { subObject.set(lastKey, value); } else { subObject[lastKey] = value; } return object; } console.error(`set error: Invalid JSON Pointer: ${pointer}`); return object; } /** * 'setCopy' function * * Copies an object and uses a JSON Pointer to set a value on the copy. * Also creates any missing sub objects or arrays to contain that value. * * If the optional fourth parameter is TRUE and the inner-most container * is an array, the function will insert the value as a new item at the * specified location in the array, rather than overwriting the existing value. * * // { object } object - The object to copy and set value in * // { Pointer } pointer - The JSON Pointer (string or array) * // value - The value to set * // { boolean } insert - insert value? * // { object } - The new object with the set value */ static setCopy(object, pointer, value, insert = false) { const keyArray = this.parse(pointer); if (keyArray !== null) { const newObject = copy(object); let subObject = newObject; for (let i = 0; i < keyArray.length - 1; ++i) { let key = keyArray[i]; if (key === '-' && isArray(subObject)) { key = subObject.length; } if (isMap(subObject) && subObject.has(key)) { subObject.set(key, copy(subObject.get(key))); subObject = subObject.get(key); } else { if (!hasOwn(subObject, key)) { subObject[key] = (keyArray[i + 1].match(/^(\d+|-)$/)) ? [] : {}; } subObject[key] = copy(subObject[key]); subObject = subObject[key]; } } const lastKey = keyArray[keyArray.length - 1]; if (isArray(subObject) && lastKey === '-') { subObject.push(value); } else if (insert && isArray(subObject) && !isNaN(+lastKey)) { subObject.splice(lastKey, 0, value); } else if (isMap(subObject)) { subObject.set(lastKey, value); } else { subObject[lastKey] = value; } return newObject; } console.error(`setCopy error: Invalid JSON Pointer: ${pointer}`); return object; } /** * 'insert' function * * Calls 'set' with insert = TRUE * * // { object } object - object to insert value in * // { Pointer } pointer - JSON Pointer (string or array) * // value - value to insert * // { object } */ static insert(object, pointer, value) { const updatedObject = this.set(object, pointer, value, true); return updatedObject; } /** * 'insertCopy' function * * Calls 'setCopy' with insert = TRUE * * // { object } object - object to insert value in * // { Pointer } pointer - JSON Pointer (string or array) * // value - value to insert * // { object } */ static insertCopy(object, pointer, value) { const updatedObject = this.setCopy(object, pointer, value, true); return updatedObject; } /** * 'remove' function * * Uses a JSON Pointer to remove a key and its attribute from an object * * // { object } object - object to delete attribute from * // { Pointer } pointer - JSON Pointer (string or array) * // { object } */ static remove(object, pointer) { const keyArray = this.parse(pointer); if (keyArray !== null && keyArray.length) { let lastKey = keyArray.pop(); const parentObject = this.get(object, keyArray); if (isArray(parentObject)) { if (lastKey === '-') { lastKey = parentObject.length - 1; } parentObject.splice(lastKey, 1); } else if (isObject(parentObject)) { delete parentObject[lastKey]; } return object; } console.error(`remove error: Invalid JSON Pointer: ${pointer}`); return object; } /** * 'has' function * * Tests if an object has a value at the location specified by a JSON Pointer * * // { object } object - object to chek for value * // { Pointer } pointer - JSON Pointer (string or array) * // { boolean } */ static has(object, pointer) { const hasValue = this.get(object, pointer, 0, null, true); return hasValue; } /** * 'dict' function * * Returns a (pointer -> value) dictionary for an object * * // { object } object - The object to create a dictionary from * // { object } - The resulting dictionary object */ static dict(object) { const results = {}; this.forEachDeep(object, (value, pointer) => { if (typeof value !== 'object') { results[pointer] = value; } }); return results; } /** * 'forEachDeep' function * * Iterates over own enumerable properties of an object or items in an array * and invokes an iteratee function for each key/value or index/value pair. * By default, iterates over items within objects and arrays after calling * the iteratee function on the containing object or array itself. * * The iteratee is invoked with three arguments: (value, pointer, rootObject), * where pointer is a JSON pointer indicating the location of the current * value within the root object, and rootObject is the root object initially * submitted to th function. * * If a third optional parameter 'bottomUp' is set to TRUE, the iterator * function will be called on sub-objects and arrays after being * called on their contents, rather than before, which is the default. * * This function can also optionally be called directly on a sub-object by * including optional 4th and 5th parameterss to specify the initial * root object and pointer. * * // { object } object - the initial object or array * // { (v: any, p?: string, o?: any) => any } function - iteratee function * // { boolean = false } bottomUp - optional, set to TRUE to reverse direction * // { object = object } rootObject - optional, root object or array * // { string = '' } pointer - optional, JSON Pointer to object within rootObject * // { object } - The modified object */ static forEachDeep(object, fn = (v) => v, bottomUp = false, pointer = '', rootObject = object) { if (typeof fn !== 'function') { console.error(`forEachDeep error: Iterator is not a function:`, fn); return; } if (!bottomUp) { fn(object, pointer, rootObject); } if (isObject(object) || isArray(object)) { for (const key of Object.keys(object)) { const newPointer = pointer + '/' + this.escape(key); this.forEachDeep(object[key], fn, bottomUp, newPointer, rootObject); } } if (bottomUp) { fn(object, pointer, rootObject); } } /** * 'forEachDeepCopy' function * * Similar to forEachDeep, but returns a copy of the original object, with * the same keys and indexes, but with values replaced with the result of * the iteratee function. * * // { object } object - the initial object or array * // { (v: any, k?: string, o?: any, p?: any) => any } function - iteratee function * // { boolean = false } bottomUp - optional, set to TRUE to reverse direction * // { object = object } rootObject - optional, root object or array * // { string = '' } pointer - optional, JSON Pointer to object within rootObject * // { object } - The copied object */ static forEachDeepCopy(object, fn = (v) => v, bottomUp = false, pointer = '', rootObject = object) { if (typeof fn !== 'function') { console.error(`forEachDeepCopy error: Iterator is not a function:`, fn); return null; } if (isObject(object) || isArray(object)) { let newObject = isArray(object) ? [...object] : { ...object }; if (!bottomUp) { newObject = fn(newObject, pointer, rootObject); } for (const key of Object.keys(newObject)) { const newPointer = pointer + '/' + this.escape(key); newObject[key] = this.forEachDeepCopy(newObject[key], fn, bottomUp, newPointer, rootObject); } if (bottomUp) { newObject = fn(newObject, pointer, rootObject); } return newObject; } else { return fn(object, pointer, rootObject); } } /** * 'escape' function * * Escapes a string reference key * * // { string } key - string key to escape * // { string } - escaped key */ static escape(key) { const escaped = key.toString().replace(/~/g, '~0').replace(/\//g, '~1'); return escaped; } /** * 'unescape' function * * Unescapes a string reference key * * // { string } key - string key to unescape * // { string } - unescaped key */ static unescape(key) { const unescaped = key.toString().replace(/~1/g, '/').replace(/~0/g, '~'); return unescaped; } /** * 'parse' function * * Converts a string JSON Pointer into a array of keys * (if input is already an an array of keys, it is returned unchanged) * * // { Pointer } pointer - JSON Pointer (string or array) * // { boolean = false } errors - Show error if invalid pointer? * // { string[] } - JSON Pointer array of keys */ static parse(pointer, errors = false) { if (!this.isJsonPointer(pointer)) { if (errors) { console.error(`parse error: Invalid JSON Pointer: ${pointer}`); } return null; } if (isArray(pointer)) { return pointer; } if (typeof pointer === 'string') { if (pointer[0] === '#') { pointer = pointer.slice(1); } if (pointer === '' || pointer === '/') { return []; } return pointer.slice(1).split('/').map(this.unescape); } } /** * 'compile' function * * Converts an array of keys into a JSON Pointer string * (if input is already a string, it is normalized and returned) * * The optional second parameter is a default which will replace any empty keys. * * // { Pointer } pointer - JSON Pointer (string or array) * // { string | number = '' } defaultValue - Default value * // { boolean = false } errors - Show error if invalid pointer? * // { string } - JSON Pointer string */ static compile(pointer, defaultValue = '', errors = false) { if (pointer === '#') { return ''; } if (!this.isJsonPointer(pointer)) { if (errors) { console.error(`compile error: Invalid JSON Pointer: ${pointer}`); } return null; } if (isArray(pointer)) { if (pointer.length === 0) { return ''; } return '/' + pointer.map(key => key === '' ? defaultValue : this.escape(key)).join('/'); } if (typeof pointer === 'string') { if (pointer[0] === '#') { pointer = pointer.slice(1); } return pointer; } } /** * 'toKey' function * * Extracts name of the final key from a JSON Pointer. * * // { Pointer } pointer - JSON Pointer (string or array) * // { boolean = false } errors - Show error if invalid pointer? * // { string } - the extracted key */ static toKey(pointer, errors = false) { const keyArray = this.parse(pointer, errors); if (keyArray === null) { return null; } if (!keyArray.length) { return ''; } return keyArray[keyArray.length - 1]; } /** * 'isJsonPointer' function * * Checks a string or array value to determine if it is a valid JSON Pointer. * Returns true if a string is empty, or starts with '/' or '#/'. * Returns true if an array contains only string values. * * // value - value to check * // { boolean } - true if value is a valid JSON Pointer, otherwise false */ static isJsonPointer(value) { if (isArray(value)) { return value.every(key => typeof key === 'string'); } else if (isString(value)) { if (value === '' || value === '#') { return true; } if (value[0] === '/' || value.slice(0, 2) === '#/') { return !/(~[^01]|~$)/g.test(value); } } return false; } /** * 'isSubPointer' function * * Checks whether one JSON Pointer is a subset of another. * * // { Pointer } shortPointer - potential subset JSON Pointer * // { Pointer } longPointer - potential superset JSON Pointer * // { boolean = false } trueIfMatching - return true if pointers match? * // { boolean = false } errors - Show error if invalid pointer? * // { boolean } - true if shortPointer is a subset of longPointer, false if not */ static isSubPointer(shortPointer, longPointer, trueIfMatching = false, errors = false) { if (!this.isJsonPointer(shortPointer) || !this.isJsonPointer(longPointer)) { if (errors) { let invalid = ''; if (!this.isJsonPointer(shortPointer)) { invalid += ` 1: ${shortPointer}`; } if (!this.isJsonPointer(longPointer)) { invalid += ` 2: ${longPointer}`; } console.error(`isSubPointer error: Invalid JSON Pointer ${invalid}`); } return; } shortPointer = this.compile(shortPointer, '', errors); longPointer = this.compile(longPointer, '', errors); return shortPointer === longPointer ? trueIfMatching : `${shortPointer}/` === longPointer.slice(0, shortPointer.length + 1); } /** * 'toIndexedPointer' function * * Merges an array of numeric indexes and a generic pointer to create an * indexed pointer for a specific item. * * For example, merging the generic pointer '/foo/-/bar/-/baz' and * the array [4, 2] would result in the indexed pointer '/foo/4/bar/2/baz' * * * // { Pointer } genericPointer - The generic pointer * // { number[] } indexArray - The array of numeric indexes * // { Map<string, number> } arrayMap - An optional array map * // { string } - The merged pointer with indexes */ static toIndexedPointer(genericPointer, indexArray, arrayMap = null) { if (this.isJsonPointer(genericPointer) && isArray(indexArray)) { let indexedPointer = this.compile(genericPointer); if (isMap(arrayMap)) { let arrayIndex = 0; return indexedPointer.replace(/\/\-(?=\/|$)/g, (key, stringIndex) => arrayMap.has(indexedPointer.slice(0, stringIndex)) ? '/' + indexArray[arrayIndex++] : key); } else { for (const pointerIndex of indexArray) { indexedPointer = indexedPointer.replace('/-', '/' + pointerIndex); } return indexedPointer; } } if (!this.isJsonPointer(genericPointer)) { console.error(`toIndexedPointer error: Invalid JSON Pointer: ${genericPointer}`); } if (!isArray(indexArray)) { console.error(`toIndexedPointer error: Invalid indexArray: ${indexArray}`); } } /** * 'toGenericPointer' function * * Compares an indexed pointer to an array map and removes list array * indexes (but leaves tuple arrray indexes and all object keys, including * numeric keys) to create a generic pointer. * * For example, using the indexed pointer '/foo/1/bar/2/baz/3' and * the arrayMap [['/foo', 0], ['/foo/-/bar', 3], ['/foo/-/bar/-/baz', 0]] * would result in the generic pointer '/foo/-/bar/2/baz/-' * Using the indexed pointer '/foo/1/bar/4/baz/3' and the same arrayMap * would result in the generic pointer '/foo/-/bar/-/baz/-' * (the bar array has 3 tuple items, so index 2 is retained, but 4 is removed) * * The structure of the arrayMap is: [['path to array', number of tuple items]...] * * * // { Pointer } indexedPointer - The indexed pointer (array or string) * // { Map<string, number> } arrayMap - The optional array map (for preserving tuple indexes) * // { string } - The generic pointer with indexes removed */ static toGenericPointer(indexedPointer, arrayMap = new Map()) { if (this.isJsonPointer(indexedPointer) && isMap(arrayMap)) { const pointerArray = this.parse(indexedPointer); for (let i = 1; i < pointerArray.length; i++) { const subPointer = this.compile(pointerArray.slice(0, i)); if (arrayMap.has(subPointer) && arrayMap.get(subPointer) <= +pointerArray[i]) { pointerArray[i] = '-'; } } return this.compile(pointerArray); } if (!this.isJsonPointer(indexedPointer)) { console.error(`toGenericPointer error: invalid JSON Pointer: ${indexedPointer}`); } if (!isMap(arrayMap)) { console.error(`toGenericPointer error: invalid arrayMap: ${arrayMap}`); } } /** * 'toControlPointer' function * * Accepts a JSON Pointer for a data object and returns a JSON Pointer for the * matching control in an Angular FormGroup. * * // { Pointer } dataPointer - JSON Pointer (string or array) to a data object * // { FormGroup } formGroup - Angular FormGroup to get value from * // { boolean = false } controlMustExist - Only return if control exists? * // { Pointer } - JSON Pointer (string) to the formGroup object */ static toControlPointer(dataPointer, formGroup, controlMustExist = false) { const dataPointerArray = this.parse(dataPointer); const controlPointerArray = []; let subGroup = formGroup; if (dataPointerArray !== null) { for (const key of dataPointerArray) { if (hasOwn(subGroup, 'controls')) { controlPointerArray.push('controls'); subGroup = subGroup.controls; } if (isArray(subGroup) && (key === '-')) { controlPointerArray.push((subGroup.length - 1).toString()); subGroup = subGroup[subGroup.length - 1]; } else if (hasOwn(subGroup, key)) { controlPointerArray.push(key); subGroup = subGroup[key]; } else if (controlMustExist) { console.error(`toControlPointer error: Unable to find "${key}" item in FormGroup.`); console.error(dataPointer); console.error(formGroup); return; } else { controlPointerArray.push(key); subGroup = { controls: {} }; } } return this.compile(controlPointerArray); } console.error(`toControlPointer error: Invalid JSON Pointer: ${dataPointer}`); } /** * 'toSchemaPointer' function * * Accepts a JSON Pointer to a value inside a data object and a JSON schema * for that object. * * Returns a Pointer to the sub-schema for the value inside the object's schema. * * // { Pointer } dataPointer - JSON Pointer (string or array) to an object * // schema - JSON schema for the object * // { Pointer } - JSON Pointer (string) to the object's schema */ static toSchemaPointer(dataPointer, schema) { if (this.isJsonPointer(dataPointer) && typeof schema === 'object') { const pointerArray = this.parse(dataPointer); if (!pointerArray.length) { return ''; } const firstKey = pointerArray.shift(); if (schema.type === 'object' || schema.properties || schema.additionalProperties) { if ((schema.properties || {})[firstKey]) { return `/properties/${this.escape(firstKey)}` + this.toSchemaPointer(pointerArray, schema.properties[firstKey]); } else if (schema.additionalProperties) { return '/additionalProperties' + this.toSchemaPointer(pointerArray, schema.additionalProperties); } } if ((schema.type === 'array' || schema.items) && (isNumber(firstKey) || firstKey === '-' || firstKey === '')) { const arrayItem = firstKey === '-' || firstKey === '' ? 0 : +firstKey; if (isArray(schema.items)) { if (arrayItem < schema.items.length) { return '/items/' + arrayItem + this.toSchemaPointer(pointerArray, schema.items[arrayItem]); } else if (schema.additionalItems) { return '/additionalItems' + this.toSchemaPointer(pointerArray, schema.additionalItems); } } else if (isObject(schema.items)) { return '/items' + this.toSchemaPointer(pointerArray, schema.items); } else if (isObject(schema.additionalItems)) { return '/additionalItems' + this.toSchemaPointer(pointerArray, schema.additionalItems); } } console.error(`toSchemaPointer error: Data pointer ${dataPointer} ` + `not compatible with schema ${schema}`); return null; } if (!this.isJsonPointer(dataPointer)) { console.error(`toSchemaPointer error: Invalid JSON Pointer: ${dataPointer}`); } if (typeof schema !== 'object') { console.error(`toSchemaPointer error: Invalid JSON Schema: ${schema}`); } return null; } /** * 'toDataPointer' function * * Accepts a JSON Pointer to a sub-schema inside a JSON schema and the schema. * * If possible, returns a generic Pointer to the corresponding value inside * the data object described by the JSON schema. * * Returns null if the sub-schema is in an ambiguous location (such as * definitions or additionalProperties) where the corresponding value * location cannot be determined. * * // { Pointer } schemaPointer - JSON Pointer (string or array) to a JSON schema * // schema - the JSON schema * // { boolean = false } errors - Show errors? * // { Pointer } - JSON Pointer (string) to the value in the data object */ static toDataPointer(schemaPointer, schema, errors = false) { if (this.isJsonPointer(schemaPointer) && typeof schema === 'object' && this.has(schema, schemaPointer)) { const pointerArray = this.parse(schemaPointer); if (!pointerArray.length) { return ''; } const firstKey = pointerArray.shift(); if (firstKey === 'properties' || (firstKey === 'items' && isArray(schema.items))) { const secondKey = pointerArray.shift(); const pointerSuffix = this.toDataPointer(pointerArray, schema[firstKey][secondKey]); return pointerSuffix === null ? null : '/' + secondKey + pointerSuffix; } else if (firstKey === 'additionalItems' || (firstKey === 'items' && isObject(schema.items))) { const pointerSuffix = this.toDataPointer(pointerArray, schema[firstKey]); return pointerSuffix === null ? null : '/-' + pointerSuffix; } else if (['allOf', 'anyOf', 'oneOf'].includes(firstKey)) { const secondKey = pointerArray.shift(); return this.toDataPointer(pointerArray, schema[firstKey][secondKey]); } else if (firstKey === 'not') { return this.toDataPointer(pointerArray, schema[firstKey]); } else if (['contains', 'definitions', 'dependencies', 'additionalItems', 'additionalProperties', 'patternProperties', 'propertyNames'].includes(firstKey)) { if (errors) { console.error(`toDataPointer error: Ambiguous location`); } } return ''; } if (errors) { if (!this.isJsonPointer(schemaPointer)) { console.error(`toDataPointer error: Invalid JSON Pointer: ${schemaPointer}`); } if (typeof schema !== 'object') { console.error(`toDataPointer error: Invalid JSON Schema: ${schema}`); } if (typeof schema !== 'object') { console.error(`toDataPointer error: Pointer ${schemaPointer} invalid for Schema: ${schema}`); } } return null; } /** * 'parseObjectPath' function * * Parses a JavaScript object path into an array of keys, which * can then be passed to compile() to convert into a string JSON Pointer. * * Based on mike-marcacci's excellent objectpath parse function: * https://github.com/mike-marcacci/objectpath * * // { Pointer } path - The object path to parse * // { string[] } - The resulting array of keys */ static parseObjectPath(path) { if (isArray(path)) { return path; } if (this.isJsonPointer(path)) { return this.parse(path); } if (typeof path === 'string') { let index = 0; const parts = []; while (index < path.length) { const nextDot = path.indexOf('.', index); const nextOB = path.indexOf('[', index); // next open bracket if (nextDot === -1 && nextOB === -1) { // last item parts.push(path.slice(index)); index = path.length; } else if (nextDot !== -1 && (nextDot < nextOB || nextOB === -1)) { // dot notation parts.push(path.slice(index, nextDot)); index = nextDot + 1; } else { // bracket notation if (nextOB > index) { parts.push(path.slice(index, nextOB)); index = nextOB; } const quote = path.charAt(nextOB + 1); if (quote === '"' || quote === '\'') { // enclosing quotes let nextCB = path.indexOf(quote + ']', nextOB); // next close bracket while (nextCB !== -1 && path.charAt(nextCB - 1) === '\\') { nextCB = path.indexOf(quote + ']', nextCB + 2); } if (nextCB === -1) { nextCB = path.length; } parts.push(path.slice(index + 2, nextCB) .replace(new RegExp('\\' + quote, 'g'), quote)); index = nextCB + 2; } else { // no enclosing quotes let nextCB = path.indexOf(']', nextOB); // next close bracket if (nextCB === -1) { nextCB = path.length; } parts.push(path.slice(index + 1, nextCB)); index = nextCB + 1; } if (path.charAt(index) === '.') { index++; } } } return parts; } console.error('parseObjectPath error: Input object path must be a string.'); } } JsonPointer.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: JsonPointer, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); JsonPointer.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: JsonPointer }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: JsonPointer, decorators: [{ type: Injectable }] }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoianNvbnBvaW50ZXIuZnVuY3Rpb25zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vcHJvamVjdHMvYWpzZi1jb3JlL3NyYy9saWIvc2hhcmVkL2pzb25wb2ludGVyLmZ1bmN0aW9ucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsa0JBQWtCLEVBQ2xCLElBQUksRUFFSixpQkFBaUIsRUFDakIsOEJBQThCLEVBQzlCLE1BQU0sRUFDTixPQUFPLEVBQ1AsVUFBVSxFQUNWLGVBQWUsRUFDaEIsTUFBTSxxQkFBcUIsQ0FBQztBQUM3QixPQUFPLEVBQUMsVUFBVSxFQUFDLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sRUFBQyxPQUFPLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUMsTUFBTSx1QkFBdUIsQ0FBQzs7QUFtQnZHLE1BQU0sT0FBTyxXQUFXO0lBRXRCOzs7Ozs7Ozs7Ozs7T0FZRztJQUNILE1BQU0sQ0FBQyxHQUFHLENBQ1IsTUFBTSxFQUFFLE9BQU8sRUFBRSxVQUFVLEdBQUcsQ0FBQyxFQUFFLFdBQW1CLElBQUksRUFDeEQsVUFBVSxHQUFHLEtBQUssRUFBRSxNQUFNLEdBQUcsS0FBSztRQUVsQyxJQUFJLE1BQU0sS0FBSyxJQUFJLEVBQUU7WUFBRSxPQUFPLFVBQVUsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7U0FBRTtRQUMvRCxJQUFJLFFBQVEsR0FBVSxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztRQUNsRCxJQUFJLE9BQU8sTUFBTSxLQUFLLFFBQVEsSUFBSSxRQUFRLEtBQUssSUFBSSxFQUFFO1lBQ25ELElBQUksU0FBUyxHQUFHLE1BQU0sQ0FBQztZQUN2QixJQUFJLFVBQVUsSUFBSSxRQUFRLENBQUMsTUFBTSxJQUFJLFFBQVEs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