167 lines
5.6 KiB
JavaScript
167 lines
5.6 KiB
JavaScript
![]() |
import { get, ReturnType } from "./get";
|
||
|
import { propertyRegex } from "./parser/jsonQueryGrammar";
|
||
|
import { split } from "./split";
|
||
|
const cp = (v) => JSON.parse(JSON.stringify(v));
|
||
|
const toString = Object.prototype.toString;
|
||
|
const getType = (v) => toString
|
||
|
.call(v)
|
||
|
.match(/\s([^\]]+)\]/)
|
||
|
.pop()
|
||
|
.toLowerCase();
|
||
|
const isProperty = new RegExp(`^("[^"]+"|${propertyRegex})$`);
|
||
|
const ignoreTypes = ["string", "number", "boolean", "null"];
|
||
|
const isArray = /^\[\d*\]$/;
|
||
|
const arrayHasIndex = /^\[(\d+)\]$/;
|
||
|
const isEscaped = /^".+"$/;
|
||
|
const isArrayProp = /(^\[\d*\]$|^\d+$)/;
|
||
|
function convertToIndex(index) {
|
||
|
return parseInt(index.replace(/^(\[|\]$)/, ""));
|
||
|
}
|
||
|
function removeEscape(property) {
|
||
|
return isEscaped.test(property)
|
||
|
? property.replace(/(^"|"$)/g, "")
|
||
|
: property;
|
||
|
}
|
||
|
function insert(array, index, value) {
|
||
|
if (array.length <= index) {
|
||
|
array[index] = value;
|
||
|
}
|
||
|
else {
|
||
|
array.splice(index, 0, value);
|
||
|
}
|
||
|
}
|
||
|
function select(workingSet, query) {
|
||
|
const nextSet = [];
|
||
|
workingSet.forEach((d) => nextSet.push(...get(d[0], query, ReturnType.ALL)));
|
||
|
return nextSet;
|
||
|
}
|
||
|
function addToArray(result, index, value, force) {
|
||
|
const target = result[0];
|
||
|
// append item?
|
||
|
if (/^\[\]$/.test(index)) {
|
||
|
target.push(value);
|
||
|
const i = target.length - 1;
|
||
|
return [target[i], i, target, `${result[3]}/${i}}`];
|
||
|
}
|
||
|
// merge array item?
|
||
|
if (force == null &&
|
||
|
getType(target[index]) === "object" &&
|
||
|
getType(value) === "object") {
|
||
|
Object.assign(target[index], value);
|
||
|
return [target[index], index, target, `${result[3]}/${index}}`];
|
||
|
}
|
||
|
if (force === set.INSERT_ITEMS ||
|
||
|
(force == null && arrayHasIndex.test(index))) {
|
||
|
const arrayIndex = convertToIndex(index);
|
||
|
insert(target, arrayIndex, value);
|
||
|
return [
|
||
|
target[arrayIndex],
|
||
|
arrayIndex,
|
||
|
target,
|
||
|
`${result[3]}/${arrayIndex}}`,
|
||
|
];
|
||
|
}
|
||
|
if (force === set.REPLACE_ITEMS || force == null) {
|
||
|
const arrayIndex = convertToIndex(index);
|
||
|
target[arrayIndex] = value;
|
||
|
return [
|
||
|
target[arrayIndex],
|
||
|
arrayIndex,
|
||
|
target,
|
||
|
`${result[3]}/${arrayIndex}}`,
|
||
|
];
|
||
|
}
|
||
|
throw new Error(`Unknown array index '${index}' with force-option '${force}'`);
|
||
|
}
|
||
|
function create(workingSet, query, keyIsArray, force) {
|
||
|
query = removeEscape(query);
|
||
|
return workingSet
|
||
|
.filter((o) => {
|
||
|
// replacing or inserting array
|
||
|
if (Array.isArray(o[0]) && isArrayProp.test(query)) {
|
||
|
return true;
|
||
|
}
|
||
|
return ignoreTypes.includes(getType(o[0][query])) === false;
|
||
|
})
|
||
|
.map((r) => {
|
||
|
const container = keyIsArray ? [] : {};
|
||
|
const o = r[0];
|
||
|
const containerType = getType(container);
|
||
|
const itemType = getType(o[query]);
|
||
|
if (Array.isArray(o) && itemType !== containerType) {
|
||
|
return addToArray(r, query, container, force);
|
||
|
}
|
||
|
o[query] = o[query] || container;
|
||
|
return [o[query], query, o, `${r[3]}/${query}`];
|
||
|
});
|
||
|
}
|
||
|
export var InsertMode;
|
||
|
(function (InsertMode) {
|
||
|
InsertMode["REPLACE_ITEMS"] = "replace";
|
||
|
InsertMode["INSERT_ITEMS"] = "insert";
|
||
|
})(InsertMode || (InsertMode = {}));
|
||
|
// for all array-indices within path, replace the values, ignoring insertion syntax /[1]/
|
||
|
set.REPLACE_ITEMS = InsertMode.REPLACE_ITEMS;
|
||
|
// for all array-indices within path, insert the values, ignoring replace syntax /1/
|
||
|
set.INSERT_ITEMS = InsertMode.INSERT_ITEMS;
|
||
|
// set.MERGE_ITEMS = "merge";
|
||
|
/**
|
||
|
* Runs query on input data and assigns a value to query-results.
|
||
|
* @param data - input data
|
||
|
* @param queryString - json-query string
|
||
|
* @param value - value to assign
|
||
|
* @param [force] - whether to replace or insert into arrays
|
||
|
*/
|
||
|
export function set(data, queryString, value, force) {
|
||
|
if (queryString == null) {
|
||
|
return cp(data);
|
||
|
}
|
||
|
queryString = queryString.replace(/(\/$)/g, "");
|
||
|
if (queryString === "") {
|
||
|
return cp(value);
|
||
|
}
|
||
|
const result = cp(data);
|
||
|
let workingSet = [[result, null, null, "#"]];
|
||
|
const path = split(queryString);
|
||
|
const property = path.pop();
|
||
|
const arrayWithoutIndex = isArray.test(property) && arrayHasIndex.test(property) === false;
|
||
|
if (isProperty.test(property) === false || arrayWithoutIndex) {
|
||
|
throw new Error(`Unsupported query '${queryString}' ending with non-property`);
|
||
|
}
|
||
|
path.forEach((query, index) => {
|
||
|
if ("__proto__" === query ||
|
||
|
"prototyped" === query ||
|
||
|
"constructor" === query) {
|
||
|
return;
|
||
|
}
|
||
|
if (isProperty.test(query) === false) {
|
||
|
workingSet = select(workingSet, query);
|
||
|
return;
|
||
|
}
|
||
|
// process property & missing data-structure
|
||
|
const nextKey = index >= path.length - 1 ? property : path[index + 1];
|
||
|
const insertArray = isArrayProp.test(nextKey);
|
||
|
workingSet = create(workingSet, query, insertArray, force);
|
||
|
});
|
||
|
workingSet.forEach((r) => {
|
||
|
let targetValue = value;
|
||
|
if (getType(value) === "function") {
|
||
|
targetValue = value(r[3], property, r[0], `${r[3]}/${property}`);
|
||
|
}
|
||
|
const d = r[0];
|
||
|
if (Array.isArray(d)) {
|
||
|
addToArray(r, property, targetValue, force);
|
||
|
}
|
||
|
else {
|
||
|
const unescapedProp = removeEscape(property);
|
||
|
if ("__proto__" === unescapedProp ||
|
||
|
"prototyped" === unescapedProp ||
|
||
|
"constructor" === unescapedProp) {
|
||
|
return;
|
||
|
}
|
||
|
d[unescapedProp] = targetValue;
|
||
|
}
|
||
|
});
|
||
|
return result;
|
||
|
}
|