@jsonjoy.com/json-expression
Version:
High-performance JSON Pointer implementation
441 lines (440 loc) • 18.2 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.objDelRaw = exports.objSetRaw = exports.entries = exports.values = exports.keys = exports.asObj = exports.reduce = exports.map = exports.filter = exports.zip = exports.indexOf2 = exports.indexOf = exports.fromEntries = exports.isInArr2 = exports.isInArr = exports.concat = exports.head = exports.asArr = exports.u8 = exports.isDateTime = exports.isTime = exports.isDate = exports.isDuration = exports.isUri = exports.isUuid = exports.isIp6 = exports.isIp4 = exports.isHostname = exports.isEmail = exports.substr = exports.ends = exports.contains = exports.starts = exports.asStr = exports.asBin = exports.member = exports.len = exports.mod = exports.slash = exports.betweenEqEq = exports.betweenEqNe = exports.betweenNeEq = exports.betweenNeNe = exports.cmp = exports.str = exports.int = exports.num = exports.type = exports.throwOnUndef = exports.get = void 0;
exports.parseVar = exports.operatorsToMap = exports.assertArity = exports.assertVariadicArity = exports.assertFixedArity = exports.literal = exports.asLiteral = exports.isLiteral = void 0;
const deepEqual_1 = require("@jsonjoy.com/util/lib/json-equal/deepEqual");
const json_pointer_1 = require("@jsonjoy.com/json-pointer");
// ----------------------------------------------------- Input operator helpers
const get = (path, data) => (0, json_pointer_1.get)(data, (0, json_pointer_1.toPath)(path));
exports.get = get;
const throwOnUndef = (value, def) => {
if (value !== undefined)
return value;
if (def === undefined)
throw new Error('NOT_FOUND');
return def;
};
exports.throwOnUndef = throwOnUndef;
// ------------------------------------------------------ Type operator helpers
const type = (value) => {
if (value === null)
return 'null';
if (Array.isArray(value))
return 'array';
if (value instanceof Uint8Array)
return 'binary';
return typeof value;
};
exports.type = type;
const num = (value) => +value || 0;
exports.num = num;
const int = (value) => ~~value;
exports.int = int;
const str = (value) => {
if (typeof value !== 'object')
return '' + value;
return JSON.stringify(value);
};
exports.str = str;
// ------------------------------------------------ Comparison operator helpers
const cmp = (a, b) => (a > b ? 1 : a < b ? -1 : 0);
exports.cmp = cmp;
const betweenNeNe = (val, min, max) => val > min && val < max;
exports.betweenNeNe = betweenNeNe;
const betweenNeEq = (val, min, max) => val > min && val <= max;
exports.betweenNeEq = betweenNeEq;
const betweenEqNe = (val, min, max) => val >= min && val < max;
exports.betweenEqNe = betweenEqNe;
const betweenEqEq = (val, min, max) => val >= min && val <= max;
exports.betweenEqEq = betweenEqEq;
// ------------------------------------------------ Arithmetic operator helpers
const slash = (a, b) => {
const divisor = (0, exports.num)(b);
if (divisor === 0)
throw new Error('DIVISION_BY_ZERO');
const res = (0, exports.num)(a) / divisor;
return Number.isFinite(res) ? res : 0;
};
exports.slash = slash;
const mod = (a, b) => {
const divisor = (0, exports.num)(b);
if (divisor === 0)
throw new Error('DIVISION_BY_ZERO');
const res = (0, exports.num)(a) % divisor;
return Number.isFinite(res) ? res : 0;
};
exports.mod = mod;
// ----------------------------------------- Generic container operator helpers
const len = (value) => {
switch (typeof value) {
case 'string':
return value.length;
case 'object': {
if (Array.isArray(value))
return value.length;
if (value instanceof Uint8Array)
return value.length;
if (!value)
return 0;
return Object.keys(value).length;
}
default:
return 0;
}
};
exports.len = len;
const member = (container, index) => {
switch (typeof container) {
case 'string': {
const i = (0, exports.int)(index);
if (i < 0 || i >= container.length)
return undefined;
return container[i];
}
case 'object': {
if (!container)
throw new Error('NOT_CONTAINER');
if (Array.isArray(container) || container instanceof Uint8Array) {
const i = (0, exports.int)(index);
if (i < 0 || i >= container.length)
return undefined;
return container[i];
}
switch (typeof index) {
case 'string':
case 'number':
return container[index];
default:
throw new Error('NOT_STRING_INDEX');
}
}
default:
throw new Error('NOT_CONTAINER');
}
};
exports.member = member;
const asBin = (value) => {
if (value instanceof Uint8Array)
return value;
throw new Error('NOT_BINARY');
};
exports.asBin = asBin;
// ---------------------------------------------------- String operator helpers
const asStr = (value) => {
if (typeof value === 'string')
return value;
throw new Error('NOT_STRING');
};
exports.asStr = asStr;
const starts = (outer, inner) => {
return (0, exports.str)(outer).startsWith((0, exports.str)(inner));
};
exports.starts = starts;
const contains = (outer, inner) => {
return (0, exports.str)(outer).indexOf((0, exports.str)(inner)) > -1;
};
exports.contains = contains;
const ends = (outer, inner) => {
return (0, exports.str)(outer).endsWith((0, exports.str)(inner));
};
exports.ends = ends;
const substr = (probablyString, from, to) => (0, exports.str)(probablyString).slice((0, exports.int)(from), (0, exports.int)(to));
exports.substr = substr;
const EMAIL_REG = /^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i;
const HOSTNAME_REG = /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i;
const IP4_REG = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/;
const IP6_REG = /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i;
const UUID_REG = /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i;
const NOT_URI_FRAGMENT_REG = /\/|:/;
const URI_REG = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
const DURATION_REG = /^P(?!$)((\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?|(\d+W)?)$/;
const DATE_REG = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
const TIME_REG = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
const DATE_TIME_SEPARATOR_REG = /t|\s/i;
const isEmail = (value) => typeof value === 'string' && EMAIL_REG.test(value);
exports.isEmail = isEmail;
const isHostname = (value) => typeof value === 'string' && HOSTNAME_REG.test(value);
exports.isHostname = isHostname;
const isIp4 = (value) => typeof value === 'string' && IP4_REG.test(value);
exports.isIp4 = isIp4;
const isIp6 = (value) => typeof value === 'string' && IP6_REG.test(value);
exports.isIp6 = isIp6;
const isUuid = (value) => typeof value === 'string' && UUID_REG.test(value);
exports.isUuid = isUuid;
const isUri = (value) => typeof value === 'string' && NOT_URI_FRAGMENT_REG.test(value) && URI_REG.test(value);
exports.isUri = isUri;
const isDuration = (value) => typeof value === 'string' && DURATION_REG.test(value);
exports.isDuration = isDuration;
const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const isLeapYear = (year) => year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
const isDate = (value) => {
if (typeof value !== 'string')
return false;
const matches = DATE_REG.exec(value);
if (!matches)
return false;
const year = +matches[1];
const month = +matches[2];
const day = +matches[3];
return month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && isLeapYear(year) ? 29 : DAYS[month]);
};
exports.isDate = isDate;
const isTime = (value) => {
if (typeof value !== 'string')
return false;
const matches = TIME_REG.exec(value);
if (!matches)
return false;
const hr = +matches[1];
const min = +matches[2];
const sec = +matches[3];
const tz = matches[4];
const tzSign = matches[5] === '-' ? -1 : 1;
const tzH = +(matches[6] || 0);
const tzM = +(matches[7] || 0);
if (tzH > 23 || tzM > 59 || !tz)
return false;
if (hr <= 23 && min <= 59 && sec < 60)
return true;
const utcMin = min - tzM * tzSign;
const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0);
return (utcHr === 23 || utcHr === -1) && (utcMin === 59 || utcMin === -1) && sec < 61;
};
exports.isTime = isTime;
const isDateTime = (str) => {
if (typeof str !== 'string')
return false;
const dateTime = str.split(DATE_TIME_SEPARATOR_REG);
return dateTime.length === 2 && (0, exports.isDate)(dateTime[0]) && (0, exports.isTime)(dateTime[1]);
};
exports.isDateTime = isDateTime;
// ---------------------------------------------------- Binary operator helpers
const u8 = (bin, pos) => {
const buf = (0, exports.asBin)(bin);
const index = (0, exports.int)(pos);
if (index < 0 || index >= buf.length)
throw new Error('OUT_OF_BOUNDS');
return buf[index];
};
exports.u8 = u8;
// ----------------------------------------------------- Array operator helpers
const asArr = (value) => {
if (Array.isArray(value))
return value;
throw new Error('NOT_ARRAY');
};
exports.asArr = asArr;
const head = (operand1, operand2) => {
const arr = (0, exports.asArr)(operand1);
const count = (0, exports.int)(operand2);
return count >= 0 ? arr.slice(0, count) : arr.slice(count);
};
exports.head = head;
const concat = (arrays) => {
const result = [];
for (const array of arrays) {
(0, exports.asArr)(array);
for (const item of array)
result.push(item);
}
return result;
};
exports.concat = concat;
const isInArr = (arr, what) => {
const arr2 = (0, exports.asArr)(arr);
const length = arr2.length;
for (let i = 0; i < length; i++)
if ((0, deepEqual_1.deepEqual)(arr2[i], what))
return true;
return false;
};
exports.isInArr = isInArr;
const isInArr2 = (arr, check) => {
const arr2 = (0, exports.asArr)(arr);
const length = arr2.length;
for (let i = 0; i < length; i++)
if (check(arr2[i]))
return true;
return false;
};
exports.isInArr2 = isInArr2;
const fromEntries = (maybeEntries) => {
const entries = (0, exports.asArr)(maybeEntries);
const result = {};
for (const maybeEntry of entries) {
const entry = (0, exports.asArr)(maybeEntry);
const [key, value] = (0, exports.asArr)(entry);
if (entry.length !== 2)
throw new Error('NOT_PAIR');
result[(0, exports.str)(key)] = value;
}
return result;
};
exports.fromEntries = fromEntries;
const indexOf = (container, item) => {
const arr = (0, exports.asArr)(container);
const length = arr.length;
for (let i = 0; i < length; i++)
if ((0, deepEqual_1.deepEqual)(arr[i], item))
return i;
return -1;
};
exports.indexOf = indexOf;
const indexOf2 = (container, check) => {
const arr = (0, exports.asArr)(container);
const length = arr.length;
for (let i = 0; i < length; i++)
if (check(arr[i]))
return i;
return -1;
};
exports.indexOf2 = indexOf2;
const zip = (maybeArr1, maybeArr2) => {
const arr1 = (0, exports.asArr)(maybeArr1);
const arr2 = (0, exports.asArr)(maybeArr2);
const length = Math.min(arr1.length, arr2.length);
const result = [];
for (let i = 0; i < length; i++)
result.push([arr1[i], arr2[i]]);
return result;
};
exports.zip = zip;
const filter = (arr, varname, vars, run) => {
const result = arr.filter((item) => {
vars.set(varname, item);
return run();
});
vars.del(varname);
return result;
};
exports.filter = filter;
const map = (arr, varname, vars, run) => {
const result = arr.map((item) => {
vars.set(varname, item);
return run();
});
vars.del(varname);
return result;
};
exports.map = map;
const reduce = (arr, initialValue, accname, varname, vars, run) => {
vars.set(accname, initialValue);
for (const item of arr) {
vars.set(varname, item);
const res = run();
vars.set(accname, res);
}
const result = vars.get(accname);
vars.del(accname);
vars.del(varname);
return result;
};
exports.reduce = reduce;
// ---------------------------------------------------- Object operator helpers
const asObj = (value) => {
if ((0, exports.type)(value) === 'object')
return value;
throw new Error('NOT_OBJECT');
};
exports.asObj = asObj;
const keys = (value) => Object.keys((0, exports.asObj)(value));
exports.keys = keys;
const values = (value) => {
const values = [];
const theKeys = (0, exports.keys)(value);
const length = theKeys.length;
for (let i = 0; i < length; i++)
values.push(value[theKeys[i]]);
return values;
};
exports.values = values;
const entries = (value) => {
const entries = [];
const theKeys = (0, exports.keys)(value);
const length = theKeys.length;
for (let i = 0; i < length; i++) {
const key = theKeys[i];
entries.push([key, value[key]]);
}
return entries;
};
exports.entries = entries;
const objSetRaw = (obj, key, value) => {
const prop = (0, exports.str)(key);
if (prop === '__proto__')
throw new Error('PROTO_KEY');
obj[prop] = value;
return obj;
};
exports.objSetRaw = objSetRaw;
const objDelRaw = (obj, key) => {
delete obj[key];
return obj;
};
exports.objDelRaw = objDelRaw;
// -------------------------------------------------------------------- Various
const isLiteral = (value) => {
if (Array.isArray(value))
return value.length === 1;
else
return true;
};
exports.isLiteral = isLiteral;
const asLiteral = (value) => {
if (Array.isArray(value)) {
if (value.length !== 1)
throw new Error('Invalid literal.');
return value[0];
}
else
return value;
};
exports.asLiteral = asLiteral;
const literal = (value) => (Array.isArray(value) ? [value] : value);
exports.literal = literal;
const assertFixedArity = (operator, arity, expr) => {
if (expr.length !== arity + 1)
throw new Error(`"${operator}" operator expects ${arity} operands.`);
};
exports.assertFixedArity = assertFixedArity;
const assertVariadicArity = (operator, expr) => {
if (expr.length < 3)
throw new Error(`"${operator}" operator expects at least two operands.`);
};
exports.assertVariadicArity = assertVariadicArity;
const assertArity = (operator, arity, expr) => {
if (!arity)
return;
if (Array.isArray(arity)) {
const [min, max] = arity;
if (expr.length < min + 1)
throw new Error(`"${operator}" operator expects at least ${min} operands.`);
if (max !== -1 && expr.length > max + 1)
throw new Error(`"${operator}" operator expects at most ${max} operands.`);
}
else if (arity !== -1)
(0, exports.assertFixedArity)(operator, arity, expr);
else
(0, exports.assertVariadicArity)(operator, expr);
};
exports.assertArity = assertArity;
const operatorsToMap = (operators) => {
const map = new Map();
for (const operator of operators) {
const [name, aliases] = operator;
map.set(name, operator);
for (const alias of aliases)
map.set(alias, operator);
}
return map;
};
exports.operatorsToMap = operatorsToMap;
const parseVar = (name) => {
if (name[0] === '/')
return ['', name];
const slashIndex = name.indexOf('/');
if (slashIndex === -1)
return [name, ''];
return [name.slice(0, slashIndex), name.slice(slashIndex)];
};
exports.parseVar = parseVar;
;