Honestly way too much went into this single commit. I am so sorry future me
This commit is contained in:
182
lib/JSONSearch/jsonSearch.ts
Normal file
182
lib/JSONSearch/jsonSearch.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
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;
|
||||
}
|
Reference in New Issue
Block a user