UNPKG

jsonuri

Version:

Use URI path to get or set data

405 lines (392 loc) 11.9 kB
/*! * jsonuri v3.4.5 * (c) 2024 @aligay * Released under the MIT License. */ var IS_NOT_A_NATURAL_NUMBER = 'is not a natural number' var MUST_BE_ARRAY = 'must be a Array' var THE_PARAMETER_IS_ILLEGAL = 'the parameter is illegal' var DIRECTION_REQUIRED = 'direction must be \'before\' | \'after\' | \'append\'' var THE_INDEX_OUT_OF_BOUNDS = 'the Index Out of Bounds' var noop = function () { } var isArray = Array.isArray var isString = function (s) { return typeof s === 'string' } var isInteger = function (n) { return Number.isInteger(n) // || typeof n === 'number' && isFinite(n) && Math.ceil(n) === n } var isNatural = function (n) { return isInteger(n) && n >= 0 } var pathReg = /\// var isComplexPath = function (s) { return pathReg.test(s) } var isObject = function (o) { // [^Undefined, Null, boolean, Number, String, Symbol] // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures var type = typeof o return o != null && (type === 'object' || type === 'function') } var toString = function (s) { /* eslint-disable-next-line */ return s + ''; } var showError = function (s) { console.error(s) } /** * 让数组的变化可被监听 * @param obj * @param key * @param value */ var setValue = function (obj, key, value) { if (!isArray(obj)) { obj[key] = value return } if (key === 'length') { /* eslint-disable-next-line */ if (!isNatural(value)) throw new Error('value: ' + value + ' ' + IS_NOT_A_NATURAL_NUMBER) if (value > obj.length) { obj.length = value } obj.splice(value) return } // if isArray, key should be a number var index = +key if (!isNatural(index)) { showError('key: ' + key + ' ' + IS_NOT_A_NATURAL_NUMBER) return } obj.length = Math.max(obj.length, index) obj.splice(index, 1, value) } /** * 让数组的删除可被监听 */ var delValue = function (obj, key) { if (isArray(obj)) { var index = +key if (!isNatural(index)) { return } obj.splice(index, 1) } else { /* eslint-disable-next-line */ delete obj[key]; } } /** * insertValue */ var insertValue = function (arr, key, value, direction) { if (direction === void 0) { direction = 'after' } if (key < 0 || key > arr.length) { throw new Error(THE_INDEX_OUT_OF_BOUNDS) } switch (direction) { case 'before': key = key - 1 break case 'append': showError('TODO') } arr.splice(key, 0, value) } var REG_PATH_SPLIT = '/' // let combingCache: any = {} var combingPathKey = function (param) { var _a var path = (_a = param.path) !== null && _a !== void 0 ? _a : '' // if (combingCache[path]) { // return combingCache[path] // } var keys if (param.keys == null) { keys = param.path.split(REG_PATH_SPLIT) } else if (!path) { keys = param.keys } keys = keys.filter(Boolean) // // 处理 a../, ../b../ 此类错误路径 待优化 // if (/\b\.\.+\/*/.test(keys.join(''))) { // throw new Error(`error path ${path || keys.join('')}`) // } // {empty} while (~keys.indexOf('')) { var _i = keys.indexOf('') keys.splice(_i, 1) } // . while (~keys.indexOf('.')) { var _i = keys.indexOf('.') keys.splice(_i, 1) } // .. while (~keys.indexOf('..')) { var _i = keys.indexOf('..') keys[_i] = keys[_i - 1] = '' /* eslint-disable-next-line */ delete keys[_i]; /* eslint-disable-next-line */ delete keys[_i - 1]; keys.splice(_i, 1) keys.splice(_i - 1, 1) } var ret = { keys: keys, path: keys.join(REG_PATH_SPLIT) } return ret } var get = function (data, path) { if (data == null) { return data } path = toString(path) if (path === '') { return data } if (!isComplexPath(path)) { return data[path] } var ret var keys = combingPathKey({ path: path }).keys if (keys.length === 0) { return data } var len = keys.length for (var i = 0; i < len; ++i) { /* eslint-disable-next-line */ ret = (i ? ret : data)[keys[i]]; if (ret == null) { break } } return ret } /** * Returns true, if given key is included in the blacklisted * keys. * @param key key for check, string. */ var isPrototypePolluted = function (key) { return ['__proto__', 'prototype', 'constructor'].includes(key) } var set = function (data, path, value) { path = toString(path) // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression if (!(data && path)) { return showError(THE_PARAMETER_IS_ILLEGAL) } // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression if (!isComplexPath(path)) { return setValue(data, path, value) } var keys = combingPathKey({ path: path }).keys for (var i = 0, len = keys.length; i < len; i++) { var key = keys[i] if (isPrototypePolluted(key)) { continue } if (data[key] == null) { data[key] = {} } if (i === len - 1) { setValue(data, key, value) } else { data = data[key] } } return data } var rm = function (data, path) { var _a path = toString(path) if (!(data && path)) { return } if (!isComplexPath(path)) { delValue(data, path) return } var parent = get(data, path + '/..') if (!parent) { return } var key = (_a = combingPathKey({ path: path }).keys.pop()) !== null && _a !== void 0 ? _a : '' delValue(parent, key) } var swap = function (data, pathA, pathB) { pathA = toString(pathA) pathB = toString(pathB) if (!(data && pathA && pathB && isString(pathA) && isString(pathB))) { showError(THE_PARAMETER_IS_ILLEGAL) return } var dataA = get(data, pathA) var dataB = get(data, pathB) set(data, pathB, dataA) set(data, pathA, dataB) } var insert = function (data, path, value, direction) { path = toString(path) if (!data) { showError(THE_PARAMETER_IS_ILLEGAL) return } if (!direction) { throw new Error(DIRECTION_REQUIRED) } var parent = get(data, path + '/..') if (!isArray(parent)) { throw new Error('insert node ' + MUST_BE_ARRAY) } var index = +combingPathKey({ path: path }).keys.pop() var toIndex = index if (direction === 'after') { toIndex = index + 1 } else if (direction === 'before') { toIndex = index } insertValue(parent, toIndex, value) } var arrPro = Array.prototype var normalizePath = function () { var path = [] for (var _i = 0; _i < arguments.length; _i++) { path[_i] = arguments[_i] } var pathArr = arrPro.concat.apply(arrPro, path).join('/').split('/') var pathStr = combingPathKey({ keys: pathArr }).path return pathStr } var formPathIsPartOfToParentPath = function (from, to) { var pathTo = to.split('/') var pathFrom = from.split('/') var parentPathTo = pathTo.slice(0, pathTo.length - 1) var parentPathForm = pathFrom.slice(0, pathTo.length - 1) if (pathTo.length > pathFrom.length) { return false } return parentPathTo.join('/') === parentPathForm.join('/') } var mv = function (data, from, to, direction) { var _a, _b, _c from = toString(from) to = toString(to) if (!(data && from && to && isString(from) && isString(to))) { showError(THE_PARAMETER_IS_ILLEGAL) return } if (from === to) { return } var DataTo = get(data, to) var dataFrom = get(data, from) var parentTo = get(data, to + '/..') var fromIndex = +((_a = combingPathKey({ path: from }).keys.pop()) !== null && _a !== void 0 ? _a : '') var toIndex = +((_b = combingPathKey({ path: to }).keys.pop()) !== null && _b !== void 0 ? _b : '') if (isArray(parentTo)) { if (!direction) { throw new Error(DIRECTION_REQUIRED) } var isInSameArray = normalizePath(from + '/..') === normalizePath(to + '/..') if (isInSameArray) { insert(data, to, dataFrom, direction) delValue(parentTo, fromIndex + (toIndex > fromIndex ? 0 : 1)) return } var isParentInSameArray = formPathIsPartOfToParentPath(from, to) if (isParentInSameArray) { var _fromIndex = +((_c = from.split('/').slice(0, to.split('/').length).pop()) !== null && _c !== void 0 ? _c : '') if (toIndex < _fromIndex) { // 如果把 from 插入 to 位置后,改变了原来 from 的位置,则要先删除后添加 rm(data, from) insert(data, to, dataFrom, direction) return } } insert(data, to, dataFrom, direction) rm(data, from) return } if (!isObject(DataTo)) { throw new Error("'" + to + "': " + DataTo + ' is primitive values') } set(data, to + '/' + from, dataFrom) rm(data, from) } var upDown = function (data, path, direction, gap) { if (gap === void 0) { gap = 1 } path = toString(path) if (!(isNatural(gap) && gap > 0)) { showError(THE_PARAMETER_IS_ILLEGAL) return } if (!(data)) { showError(THE_PARAMETER_IS_ILLEGAL) return } /* eslint-disable-next-line */ var parent = get(data, path + '/..'); if (!isArray(parent)) { showError(MUST_BE_ARRAY) return } var len = parent.length var index = +combingPathKey({ path: path }).keys.pop() if (!isNatural(index) || index > len - 1) { return } var toIndex = index + direction * gap if (toIndex <= 0) { toIndex = 0 } if (toIndex > len - 1) { toIndex = len - 1 } var fromData = parent[index] delValue(parent, index) insertValue(parent, toIndex, fromData) } var up = function (data, path, gap) { upDown(data, path, -1, gap) } var down = function (data, path, gap) { upDown(data, path, 1, gap) } var _computePath = function (path, direction) { var index = +combingPathKey({ path: path }).keys.pop() if (!isInteger(index)) { return null } if (direction === 'prev') { return normalizePath(path, '..', index - 1) } if (direction === 'next') { return normalizePath(path, '..', index + 1) } return null } // check circular obj var isCircular = function (obj, _seen) { if (_seen === void 0) { _seen = [] } if (!isObject(obj)) { return false } _seen.push(obj) for (var key in obj) { /* eslint-disable-next-line */ if (obj.hasOwnProperty(key)) { var val = obj[key] if (isObject(val)) { if (~_seen.indexOf(val) || isCircular(val, _seen.slice())) { return true } } } } return false } var objectForeach = function (obj, callback) { var isBreak = false var _break = function () { isBreak = true } for (var _i = 0, _a = Object.keys(obj); _i < _a.length; _i++) { var prop = _a[_i] if (isBreak) { break } callback(obj[prop], prop, obj, { _break: _break }) } } var walk = function (obj, descentionFn, ascentionFn) { if (obj === void 0) { obj = {} } if (descentionFn === void 0) { descentionFn = noop } if (ascentionFn === void 0) { ascentionFn = noop } if (isCircular(obj)) { throw new Error('obj is a circular structure') } var path = [] var _walk = function (obj) { objectForeach(obj, function (val, key, parent, _a) { var _break = _a._break var isBreak = false var _gBreak = function () { _break() isBreak = true if (isArray(parent)) { path.pop() } } path.push(key) descentionFn(val, key, parent, { path: normalizePath(path), _break: _gBreak }) path.pop() if (isObject(val)) { path.push(key) if (isBreak) { return } _walk(val) path.pop() ascentionFn(val, key, parent, { path: normalizePath(path), _break: _gBreak }) } }) return obj } return _walk(obj) } export { _computePath, down, get, insert, isCircular, mv, normalizePath, rm, set, swap, up, walk }