183 lines
4.2 KiB
TypeScript
183 lines
4.2 KiB
TypeScript
export type JSONObject = { [key: string]: any };
|
|
export type JSONArray = JSONObject[];
|
|
|
|
export function searchJSON(json: string | JSONObject | JSONArray, query: string): string | JSONArray | JSONObject | string[] | undefined {
|
|
if (typeof json === 'string') {
|
|
json = JSON.parse(json);
|
|
}
|
|
|
|
const props = query.split('.');
|
|
const currentArray: JSONArray = Array.isArray(json) ? json : [json];
|
|
|
|
for (let i = 0; i < currentArray.length; i++) {
|
|
let current = currentArray[i];
|
|
|
|
for (let j = 0; j < props.length; j++) {
|
|
const prop = props[j];
|
|
// let isArrayQuery = false;
|
|
let arrayProp: string, arrayQuery: string | string[], arrayIndex: number;
|
|
|
|
if (prop.includes('[') && prop.includes(']')) {
|
|
// isArrayQuery = true;
|
|
[arrayProp, arrayQuery] = prop.split(/[\[\]]/g).filter(Boolean);
|
|
|
|
if (arrayQuery.includes('/')) {
|
|
arrayQuery = arrayQuery.split('/');
|
|
} else {
|
|
arrayQuery = [arrayQuery];
|
|
}
|
|
|
|
arrayIndex = 0;
|
|
current = current[arrayProp][arrayIndex];
|
|
while (current) {
|
|
if (checkArrayQuery(current, arrayQuery)) {
|
|
break;
|
|
}
|
|
current = current[arrayProp][++arrayIndex];
|
|
}
|
|
} else {
|
|
current = current[prop];
|
|
}
|
|
|
|
if (current === undefined) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (current !== undefined) {
|
|
return current;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function checkArrayQuery(obj: JSONObject, queryArr: string[]): boolean {
|
|
return queryArr.every((query) => {
|
|
let [prop, operator, valueStr] = query.split(/([=!><]+)/);
|
|
prop = obj[prop];
|
|
const values = valueStr.split('/');
|
|
|
|
if (values.length > 1) {
|
|
return values.includes(prop);
|
|
}
|
|
|
|
switch (operator) {
|
|
case'=':
|
|
return areSimilar(prop, valueStr);
|
|
case '==':
|
|
return prop == valueStr;
|
|
case '!=':
|
|
return prop != valueStr;
|
|
case '>':
|
|
return prop > valueStr;
|
|
case '>=':
|
|
return prop >= valueStr;
|
|
case '<':
|
|
return prop < valueStr;
|
|
case '<=':
|
|
return prop <= valueStr;
|
|
case '><':
|
|
return includes(prop, valueStr);
|
|
default:
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
|
|
function areSimilar<T extends string | number>(val1: T, val2: T, tolerance = .05, useRatio?: boolean): boolean {
|
|
if ((typeof val1 === 'number' || isNaN(Number(val1))) && (typeof val2 === 'number' || isNaN(Number(val2)))) {
|
|
switch (!useRatio || val2 == 0) {
|
|
case true: {
|
|
return Math.abs(Number(val1) - Number(val2)) < tolerance;
|
|
}
|
|
case false: {
|
|
const ratio = Number(val1) / (Number(val2))
|
|
return ratio >= 1 - tolerance && ratio <= 1 + tolerance;
|
|
}
|
|
}
|
|
}
|
|
|
|
const v1 = val1.toString();
|
|
const v2 = val2.toString();
|
|
|
|
const dist = levenshtein(v1, v2);
|
|
const diff = dist / ((v1.length + v2.length) / 2);
|
|
return useRatio ? diff <= 1 + tolerance && diff >= 1 - tolerance : diff <= Math.round(tolerance);
|
|
}
|
|
|
|
function levenshtein(str1: string, str2: string): number {
|
|
const cost: number[][] = [];
|
|
const n = str1.length;
|
|
const m = str2.length;
|
|
let i, j;
|
|
|
|
const minimum = (a: number, b: number, c: number) => {
|
|
let min = a;
|
|
if (b < min) {
|
|
min = b;
|
|
}
|
|
if (c < min) {
|
|
min = c;
|
|
}
|
|
return min;
|
|
}
|
|
|
|
if (n == 0) {
|
|
return Infinity;
|
|
}
|
|
if (m == 0) {
|
|
return Infinity;
|
|
}
|
|
|
|
for (let i = 0; i <= n; i++) {
|
|
cost[i] = [];
|
|
}
|
|
|
|
for (i = 0; i <= n; i++) {
|
|
cost[i][0] = i;
|
|
}
|
|
|
|
for (j = 0; j <= m; j++) {
|
|
cost[0][j] = j;
|
|
}
|
|
|
|
for (i = 1; i <= n; i++) {
|
|
|
|
const x = str1.charAt(i - 1);
|
|
|
|
for (j = 1; j <= m; j++) {
|
|
|
|
const y = str2.charAt(j - 1);
|
|
|
|
if (x == y) {
|
|
|
|
cost[i][j] = cost[i - 1][j - 1];
|
|
|
|
} else {
|
|
|
|
cost[i][j] = 1 + minimum(cost[i - 1][j - 1], cost[i][j - 1], cost[i - 1][j]);
|
|
}
|
|
|
|
}//endfor
|
|
|
|
}//endfor
|
|
|
|
return cost[n][m];
|
|
}
|
|
|
|
function includes<T extends string | number>(val1: T | T[], val2: T) {
|
|
if (Array.isArray(val1)) return val1.includes(val2);
|
|
else {
|
|
const v1 = val1.toString();
|
|
const v2 = val2.toString();
|
|
|
|
return v1.includes(v2);
|
|
}
|
|
}
|
|
|
|
function normalize(val: any) {
|
|
if (typeof val === 'string') return val.normalize();
|
|
return val;
|
|
}
|