@jsonquerylang/jsonquery
Advanced tools
Comparing version 1.6.0 to 2.0.0
@@ -1,3 +0,3 @@ | ||
import { Evaluator, JSONQuery, JSONQueryOptions } from './types'; | ||
export declare function compile(query: JSONQuery, options?: JSONQueryOptions): Evaluator; | ||
import { Function, JSONQuery, JSONQueryOptions } from './types'; | ||
export declare function compile(query: JSONQuery, options?: JSONQueryOptions): Function; | ||
//# sourceMappingURL=compile.d.ts.map |
@@ -1,36 +0,3 @@ | ||
import { JSONPath, JSONProperty, JSONQuery, JSONQueryObject } from './types'; | ||
export declare const get: (path: JSONPath | JSONProperty) => (data: unknown) => any; | ||
export declare const string: (text: string) => () => string; | ||
export declare const pipe: (entries: JSONQuery[]) => (data: unknown) => unknown; | ||
export declare const object: (query: JSONQueryObject) => (data: unknown) => {}; | ||
export declare const map: <T>(callback: JSONQuery) => (data: T[]) => unknown[]; | ||
export declare const filter: <T>(...predicate: JSONQuery[]) => (data: T[]) => T[]; | ||
export declare const sort: <T>(path?: JSONPath | JSONProperty, direction?: "asc" | "desc") => (data: T[]) => T[]; | ||
export declare const pick: (...paths: (JSONPath | JSONProperty)[]) => (data: Record<string, unknown>) => unknown; | ||
export declare const groupBy: <T>(path: JSONPath | JSONProperty) => (data: T[]) => {}; | ||
export declare const keyBy: <T>(path: JSONPath | JSONProperty) => (data: T[]) => {}; | ||
export declare const flatten: () => (data: unknown[]) => unknown[]; | ||
export declare const uniq: () => <T>(data: T[]) => T[]; | ||
export declare const uniqBy: <T>(path: JSONPath | JSONProperty) => (data: T[]) => T[]; | ||
export declare const not: (query: JSONQuery) => (data: unknown) => boolean; | ||
export declare const exists: (path: JSONPath) => (data: unknown) => boolean; | ||
export declare const limit: (count: number) => <T>(data: T[]) => T[]; | ||
export declare const keys: () => { | ||
(o: object): string[]; | ||
(o: {}): string[]; | ||
}; | ||
export declare const values: () => { | ||
<T>(o: { | ||
[s: string]: T; | ||
} | ArrayLike<T>): T[]; | ||
(o: {}): any[]; | ||
}; | ||
export declare const prod: () => (data: number[]) => number; | ||
export declare const sum: () => (data: number[]) => number; | ||
export declare const average: () => (data: number[]) => number; | ||
export declare const min: () => (data: number[]) => number; | ||
export declare const max: () => (data: number[]) => number; | ||
export declare const abs: () => (x: number) => number; | ||
export declare const round: (digits?: number) => (data: number) => number; | ||
export declare const size: () => <T>(data: T[]) => number; | ||
import { FunctionBuildersMap } from './types'; | ||
export declare const functions: FunctionBuildersMap; | ||
//# sourceMappingURL=functions.d.ts.map |
@@ -1,159 +0,148 @@ | ||
const g = (t) => Array.isArray(t), x = (t) => t && typeof t == "object" && !g(t), b = (t) => typeof t == "string", o = (t) => g(t) ? (n) => { | ||
let e = n; | ||
for (const r of t) | ||
e = e == null ? void 0 : e[r]; | ||
return e; | ||
} : (n) => n == null ? void 0 : n[t], S = (t) => () => t, h = (t) => { | ||
const n = t.map((e) => u(e)); | ||
return (e) => n.reduce((r, c) => c(r), e); | ||
}, y = (t) => { | ||
const n = Object.keys(t).map((e) => [e, u(t[e])]); | ||
return (e) => { | ||
const r = {}; | ||
return n.forEach(([c, s]) => r[c] = s(e)), r; | ||
const m = (t) => Array.isArray(t), a = (t) => t && typeof t == "object" && !m(t), b = (t) => typeof t == "string"; | ||
function s(t) { | ||
return (...n) => { | ||
const e = n.map((c) => u(c)), r = e[0], o = e[1]; | ||
return e.length === 1 ? (c) => t(r(c)) : e.length === 2 ? (c) => t(r(c), o(c)) : (c) => t(...e.map((i) => i(c))); | ||
}; | ||
}, v = (t) => { | ||
const n = u(t); | ||
return (e) => e.map(n); | ||
}, M = (...t) => { | ||
const n = u(t.length === 1 ? t[0] : t); | ||
return (e) => e.filter(n); | ||
}, E = (t = [], n) => { | ||
const e = o(t), r = n === "desc" ? -1 : 1; | ||
function c(s, i) { | ||
const f = e(s), j = e(i); | ||
return f > j ? r : f < j ? -r : 0; | ||
} | ||
return (s) => s.slice().sort(c); | ||
}, N = (...t) => { | ||
const n = t.map((e) => [ | ||
b(e) ? e : e[e.length - 1], | ||
o(e) | ||
]); | ||
return (e) => g(e) ? e.map((r) => O(r, n)) : O(e, n); | ||
}, O = (t, n) => { | ||
const e = {}; | ||
return n.forEach(([r, c]) => { | ||
e[r] = c(t); | ||
}), e; | ||
}, _ = (t) => { | ||
const n = o(t); | ||
return (e) => { | ||
const r = {}; | ||
for (const c of e) { | ||
const s = n(c); | ||
r[s] ? r[s].push(c) : r[s] = [c]; | ||
} | ||
const l = { | ||
get: (...t) => { | ||
if (t.length === 0) | ||
return (n) => n; | ||
if (t.length === 1) { | ||
const n = t[0]; | ||
return (e) => e == null ? void 0 : e[n]; | ||
} | ||
return r; | ||
}; | ||
}, d = (t) => { | ||
const n = o(t); | ||
return (e) => { | ||
const r = {}; | ||
return e.forEach((c) => { | ||
const s = n(c); | ||
r[s] = r[s] ?? c; | ||
}), r; | ||
}; | ||
}, w = () => (t) => t.flat(), A = () => (t) => [...new Set(t)], B = (t) => (n) => Object.values(_(t)(n)).map((e) => e[0]), z = (t) => { | ||
const n = u(t); | ||
return (e) => !n(e); | ||
}, C = (t) => { | ||
const n = o(t); | ||
return (e) => n(e) !== void 0; | ||
}, F = (t) => (n) => n.slice(0, t), P = () => Object.keys, R = () => Object.values, T = () => (t) => t.reduce((n, e) => n * e), k = () => (t) => t.reduce((n, e) => n + e), V = () => (t) => k()(t) / t.length, D = () => (t) => Math.min(...t), G = () => (t) => Math.max(...t), H = () => Math.abs, I = (t = 0) => (n) => +(Math.round(+(n + "e" + t)) + "e" + -t), J = () => (t) => t.length, K = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ | ||
__proto__: null, | ||
abs: H, | ||
average: V, | ||
exists: C, | ||
filter: M, | ||
flatten: w, | ||
get: o, | ||
groupBy: _, | ||
keyBy: d, | ||
keys: P, | ||
limit: F, | ||
map: v, | ||
max: G, | ||
min: D, | ||
not: z, | ||
object: y, | ||
pick: N, | ||
pipe: h, | ||
prod: T, | ||
round: I, | ||
size: J, | ||
sort: E, | ||
string: S, | ||
sum: k, | ||
uniq: A, | ||
uniqBy: B, | ||
values: R | ||
}, Symbol.toStringTag, { value: "Module" })), m = (t, n = !1) => (e, r) => { | ||
const c = u(e), s = n && b(r) ? () => r : u(r); | ||
return (i) => t(c(i), s(i)); | ||
}, p = (t, n) => Object.keys(t).reduce((e, r) => (e[r] = n(t[r]), e), {}), L = { | ||
"+": (t, n) => t + n, | ||
"-": (t, n) => t - n, | ||
"*": (t, n) => t * n, | ||
"/": (t, n) => t / n, | ||
"^": (t, n) => t ** n, | ||
"%": (t, n) => t % n | ||
}, Q = { | ||
and: (t, n) => t && n, | ||
or: (t, n) => t || n | ||
}, U = { | ||
"==": (t, n) => t === n, | ||
// we use strict comparison | ||
">": (t, n) => t > n, | ||
">=": (t, n) => t >= n, | ||
"<": (t, n) => t < n, | ||
"<=": (t, n) => t <= n, | ||
"!=": (t, n) => t !== n | ||
// we use strict comparison | ||
}, W = { | ||
...p(L, m), | ||
...p(Q, m), | ||
...p(U, (t) => m(t, !0)), | ||
return (n) => { | ||
let e = n; | ||
for (const r of t) | ||
e = e == null ? void 0 : e[r]; | ||
return e; | ||
}; | ||
}, | ||
map: (t) => { | ||
const n = u(t); | ||
return (e) => e.map(n); | ||
}, | ||
filter: (...t) => { | ||
const n = u(t.length === 1 ? t[0] : t); | ||
return (e) => e.filter(n); | ||
}, | ||
sort: (t = ["get"], n) => { | ||
const e = u(t), r = n === "desc" ? -1 : 1; | ||
function o(c, i) { | ||
const f = e(c), p = e(i); | ||
return f > p ? r : f < p ? -r : 0; | ||
} | ||
return (c) => c.slice().sort(o); | ||
}, | ||
pick: (...t) => { | ||
const n = t.map( | ||
([r, ...o]) => [o[o.length - 1], l.get(...o)] | ||
), e = (r, o) => { | ||
const c = {}; | ||
return o.forEach(([i, f]) => c[i] = f(r)), c; | ||
}; | ||
return (r) => m(r) ? r.map((o) => e(o, n)) : e(r, n); | ||
}, | ||
groupBy: (t) => { | ||
const n = u(t); | ||
return (e) => { | ||
const r = {}; | ||
for (const o of e) { | ||
const c = n(o); | ||
r[c] ? r[c].push(o) : r[c] = [o]; | ||
} | ||
return r; | ||
}; | ||
}, | ||
keyBy: (t) => { | ||
const n = u(t); | ||
return (e) => { | ||
const r = {}; | ||
return e.forEach((o) => { | ||
const c = n(o); | ||
r[c] = r[c] ?? o; | ||
}), r; | ||
}; | ||
}, | ||
flatten: () => (t) => t.flat(), | ||
uniq: () => (t) => [...new Set(t)], | ||
uniqBy: (t) => (n) => Object.values(l.groupBy(t)(n)).map((e) => e[0]), | ||
limit: (t) => (n) => n.slice(0, t), | ||
size: () => (t) => t.length, | ||
keys: () => Object.keys, | ||
values: () => Object.values, | ||
prod: () => (t) => t.reduce((n, e) => n * e), | ||
sum: () => (t) => t.reduce((n, e) => n + e), | ||
average: () => (t) => l.sum()(t) / t.length, | ||
min: () => (t) => Math.min(...t), | ||
max: () => (t) => Math.max(...t), | ||
in: (t, n) => { | ||
const e = o(t); | ||
const e = u(t); | ||
return (r) => n.includes(e(r)); | ||
}, | ||
"not in": (t, n) => { | ||
const e = o(t); | ||
const e = u(t); | ||
return (r) => !n.includes(e(r)); | ||
}, | ||
regex: (t, n, e) => { | ||
const r = new RegExp(n, e), c = o(t); | ||
return (s) => r.test(c(s)); | ||
} | ||
}, l = [K], a = [W]; | ||
const r = new RegExp(n, e), o = u(t); | ||
return (c) => r.test(o(c)); | ||
}, | ||
and: s((t, n) => t && n), | ||
or: s((t, n) => t || n), | ||
not: s((t) => !t), | ||
exists: s((t) => t !== void 0), | ||
eq: s((t, n) => t === n), | ||
gt: s((t, n) => t > n), | ||
gte: s((t, n) => t >= n), | ||
lt: s((t, n) => t < n), | ||
lte: s((t, n) => t <= n), | ||
ne: s((t, n) => t !== n), | ||
add: s((t, n) => t + n), | ||
subtract: s((t, n) => t - n), | ||
multiply: s((t, n) => t * n), | ||
divide: s((t, n) => t / n), | ||
pow: s((t, n) => t ** n), | ||
mod: s((t, n) => t % n), | ||
abs: s(Math.abs), | ||
round: s((t, n = 0) => +(Math.round(+(t + "e" + n)) + "e" + -n)) | ||
}, g = [l]; | ||
function u(t, n) { | ||
l.unshift({ ...l[0], ...n == null ? void 0 : n.functions }), a.unshift({ ...a[0], ...n == null ? void 0 : n.operators }); | ||
g.unshift({ ...g[0], ...n == null ? void 0 : n.functions }); | ||
try { | ||
const e = X(t); | ||
const e = h(t, g[0]); | ||
return (r) => { | ||
try { | ||
return e(r); | ||
} catch (c) { | ||
throw c.jsonquery = [{ data: r, query: t }, ...c.jsonquery ?? []], c; | ||
} catch (o) { | ||
throw o.jsonquery = [{ data: r, query: t }, ...o.jsonquery ?? []], o; | ||
} | ||
}; | ||
} finally { | ||
l.shift(), a.shift(); | ||
g.shift(); | ||
} | ||
} | ||
function X(t) { | ||
if (x(t)) | ||
return y(t); | ||
if (g(t)) { | ||
const [n, ...e] = t, r = l[0][n]; | ||
if (r) | ||
return r(...e); | ||
const [c, s, ...i] = t, f = a[0][s]; | ||
return f ? f(c, ...i) : h(t); | ||
} | ||
return b(t) ? o(t) : () => t; | ||
function h(t, n) { | ||
return m(t) ? b(t[0]) ? j(t, n) : y(t) : a(t) ? d(t) : () => t; | ||
} | ||
function Y(t, n, e) { | ||
function j(t, n) { | ||
const [e, ...r] = t, o = n[e]; | ||
if (!o) | ||
throw new Error(`Unknown function "${e}"`); | ||
return o(...r); | ||
} | ||
function y(t) { | ||
const n = t.map((e) => u(e)); | ||
return (e) => n.reduce((r, o) => o(r), e); | ||
} | ||
function d(t) { | ||
const n = Object.keys(t).map((e) => [e, u(t[e])]); | ||
return (e) => { | ||
const r = {}; | ||
return n.forEach(([o, c]) => r[o] = c(e)), r; | ||
}; | ||
} | ||
function k(t, n, e) { | ||
return u(n, e)(t); | ||
@@ -163,4 +152,4 @@ } | ||
u as compile, | ||
Y as jsonquery | ||
k as jsonquery | ||
}; | ||
//# sourceMappingURL=jsonquery.js.map |
@@ -1,22 +0,18 @@ | ||
export type JSONPrimitive = number | boolean | null; | ||
export type JSONProperty = string; | ||
export type JSONPath = JSONProperty[]; | ||
export type JSONQueryPipe = JSONQuery[]; | ||
export type JSONQueryFunction = [name: string, ...args: JSONQuery[]]; | ||
export type JSONQueryOperator = [left: JSONQuery, op: string, ...right: JSONQuery[]]; | ||
export type JSONQueryPipe = JSONQuery[]; | ||
export type JSONQueryObject = { | ||
[key: string]: JSONQuery; | ||
}; | ||
export type JSONQuery = JSONQueryFunction | JSONQueryOperator | JSONQueryPipe | JSONQueryObject | JSONPath | JSONProperty | JSONPrimitive; | ||
export type JSONQueryPrimitive = string | number | boolean | null; | ||
export type JSONQuery = JSONQueryFunction | JSONQueryPipe | JSONQueryObject | JSONQueryPrimitive; | ||
export type JSONProperty = string; | ||
export type JSONPath = JSONProperty[]; | ||
export type JSONQueryProperty = ['get', path?: string | JSONPath]; | ||
export interface JSONQueryOptions { | ||
functions?: FunctionsMap; | ||
operators?: OperatorMap; | ||
functions?: FunctionBuildersMap; | ||
} | ||
export type Evaluator = (data: unknown) => unknown; | ||
export type FunctionCompiler = (...args: JSONQuery[]) => Evaluator; | ||
export type FunctionsMap = Record<string, FunctionCompiler>; | ||
export type OperatorCompiler = (left: JSONQuery, ...right: JSONQuery[]) => Evaluator; | ||
export type OperatorMap = Record<string, OperatorCompiler>; | ||
export type Operator = (a: unknown, b: unknown) => unknown; | ||
export type Getter = [key: string, Evaluator]; | ||
export type Function = (data: unknown) => unknown; | ||
export type FunctionBuilder = (...args: JSONQuery[]) => Function; | ||
export type FunctionBuildersMap = Record<string, FunctionBuilder>; | ||
export type Getter = [key: string, Function]; | ||
//# sourceMappingURL=types.d.ts.map |
{ | ||
"name": "@jsonquerylang/jsonquery", | ||
"version": "1.6.0", | ||
"version": "2.0.0", | ||
"description": "A small, flexible, and expandable JSON query language", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
310
README.md
@@ -13,7 +13,7 @@ # JSON Query | ||
- Small (just `1.6 kB` when minified and gzipped!) | ||
- Small (just `1.4 kB` when minified and gzipped!) | ||
- Feature rich (40+ powerful functions and operators) | ||
- Serializable (it is JSON) | ||
- Easy to parse | ||
- Expressive | ||
- Easy to understand and remember | ||
- Serializable (it is JSON) | ||
- Feature rich (40+ powerful functions and operators) | ||
- Expandable | ||
@@ -29,3 +29,3 @@ | ||
- [JavaScript API](#javascript-api) | ||
- [Limitations](#limitations) | ||
- [Gotchas](#gotchas) | ||
- [Development](#development) | ||
@@ -35,6 +35,5 @@ - [Motivation](#motivation) | ||
External links: | ||
External pages: | ||
- [Function reference](reference/functions.md) | ||
- [Operator reference](reference/operators.md) | ||
@@ -67,6 +66,6 @@ ## Installation | ||
const names = jsonquery(data, [ | ||
["friends"], | ||
["filter", "city", "==", "New York"], | ||
["sort", "age"], | ||
["pick", "name", "age"] | ||
["get", "friends"], | ||
["filter", ["eq", ["get", "city"], "New York"]], | ||
["sort", ["get", "age"]], | ||
["pick", ["get", "name"], ["get", "age"]] | ||
]) | ||
@@ -84,8 +83,8 @@ // names = [ | ||
const result = jsonquery(data, [ | ||
["friends"], | ||
["get", "friends"], | ||
{ | ||
"names": ["map", "name"], | ||
"names": ["map", ["get", "name"]], | ||
"count": ["size"], | ||
"averageAge": [ | ||
["map", "age"], | ||
["map", ["get", "age"]], | ||
["average"] | ||
@@ -101,3 +100,3 @@ ] | ||
// use operators + - * / to do calculations | ||
// use mathematical functions like add, subtract, multiply and divide to do calculations | ||
const shoppingCart = [ | ||
@@ -108,3 +107,3 @@ { "name": "bread", "price": 2.5, "quantity": 2 }, | ||
const totalPrice = jsonquery(shoppingCart, [ | ||
["map", ["price", "*", "quantity"]], | ||
["map", ["multiply", ["get", "price"], ["get", "quantity"]]], | ||
["sum"] | ||
@@ -131,3 +130,3 @@ ]) | ||
The `jsonquery` query language is written in JSON and has the following building blocks: _functions_, _operators_, _properties_, _paths_, _pipes_, and _objects_. When writing a JSON Query, you compose a ["pipe"](https://medium.com/@efeminella/the-pipe-operator-a-glimpse-into-the-future-of-functional-javascript-7ebb578887a4) or a ["chain"](https://en.wikipedia.org/wiki/Method_chaining) of operations to be applied to the data. It resembles chaining like in [Lodash](https://lodash.com/docs/4.17.15#chain) or just [in JavaScript](https://medium.com/backticks-tildes/understanding-method-chaining-in-javascript-647a9004bd4f) itself using methods like `map` and `filter`. | ||
The `jsonquery` query language is written in JSON and has the following building blocks: _functions_, _pipes_, and _objects_. When writing a JSON Query, you compose a ["pipe"](https://medium.com/@efeminella/the-pipe-operator-a-glimpse-into-the-future-of-functional-javascript-7ebb578887a4) or a ["chain"](https://en.wikipedia.org/wiki/Method_chaining) of operations to be applied to the data. It resembles chaining like in [Lodash](https://lodash.com/docs/4.17.15#chain) or just [in JavaScript](https://medium.com/backticks-tildes/understanding-method-chaining-in-javascript-647a9004bd4f) itself using methods like `map` and `filter`. | ||
@@ -149,10 +148,7 @@ The examples in the following section are based on querying the following data: | ||
| Category | Syntax | Example | | ||
|-----------------------------------|-------------------------------------------|------------------------------------------------| | ||
| [Function](#functions) | `[name, argument1, argument2, ...]` | `["sort", ["address", "city"], "asc"]` | | ||
| [Operator](#operators) | `[left, operator, right]` | `[["address", "city"], "==", "New York"]` | | ||
| [Property](#properties-and-paths) | `"property"` | `"age"` | | ||
| [Path](#properties-and-paths) | `[property1, property2, ...]` | `["address", "city"]` | | ||
| [Pipe](#pipes) | `[query1, query1, ...]` | `[["sort", "age"], ["pick", "name", "age"]]` | | ||
| [Object](#objects) | `{"prop1": query1, "prop2": query2, ...}` | `{"names": ["map", "name"], "total": ["sum"]}` | | ||
| Category | Syntax | Example | | ||
|------------------------|-------------------------------------------|------------------------------------------------| | ||
| [Function](#functions) | `[name, argument1, argument2, ...]` | `["sort", ["get", "age"], "asc"]` | | ||
| [Pipe](#pipes) | `[query1, query1, ...]` | `[["sort", "age"], ["pick", "name", "age"]]` | | ||
| [Object](#objects) | `{"prop1": query1, "prop2": query2, ...}` | `{"names": ["map", "name"], "total": ["sum"]}` | | ||
@@ -163,112 +159,36 @@ The following sections explain the syntax in more detail. | ||
At the core of the query language, we have a _function_ call which described by an array with the function name as first item followed by optional function arguments. The following example will look up the `sort` function and then call it like `sort(data, 'age', 'asc')`. Here, `data` is the input and should be an array with objects which will be sorted in ascending by the property `age`: | ||
At the core of the query language, we have a _function_ call which described by an array with the function name as first item followed by optional function arguments. The following example will look up the `sort` function and then call it like `sort(data, (item) => item.age, 'asc')`. Here, `data` is the input and should be an array with objects which will be sorted in ascending by the property `age`: | ||
```json | ||
["sort", "age", "asc"] | ||
["sort", ["get", "age"], "asc"] | ||
``` | ||
Nested properties can be specified using a _path_: an array with properties. The following example will sort an array in descending order by a nested property `city` inside an object `address`: | ||
An important function is the function `get`. It allows to get a property from an object: | ||
```json | ||
["sort", ["address", "city"], "desc"] | ||
["get", "age"] | ||
``` | ||
See section [Function reference](reference/functions.md) for a detailed overview of all available functions. | ||
A nested property can be retrieved by specifying multiple properties. The following path for example describes the value of a nested property `city` inside an object `address`: | ||
### Operators | ||
An operator is an array with a left side value, the operator, and a right side value. In the following example, the operator describes that the property `age` of an object must be 18 or larger: | ||
```json | ||
["age", ">", 18] | ||
["get", "address", "city"] | ||
``` | ||
or here an example where an operator checks whether a nested property `city` inside an object `address` has the value `"New York"`. | ||
To get the current value itself, just specify `["get"]` without properties: | ||
```json | ||
[["address", "city"], "==", "New York"] | ||
["multiply", ["get"], 2] | ||
``` | ||
Operators are mostly used inside the `"filter"` function, for example: | ||
See section [Function reference](reference/functions.md) for a detailed overview of all available functions. | ||
```json | ||
["filter", [["address", "city"], "==", "New York"]] | ||
``` | ||
See section [Operator reference](reference/operators.md) for a detailed overview of all available operators. | ||
<details> | ||
<summary><b>Special cases regarding operators</b></summary> | ||
There are two special cases regarding operators: relational operators interpret the left side as a property when it is a string, and interpret the right side as a text when it is a string. | ||
1. All relational operators (`==`, `>`, `>=`, `<`, `<=`, `!=`) will interpret a string on the right side as a _text_ and not as a _property_ because this is a very common use case (like the "New York" example above). To specify a property on the right side of an operator, it must be changed into a _path_ by enclosing it in brackets. | ||
```js | ||
// WRONG: "age" is interpreted as text | ||
["filter", [18, "<", "age"]] | ||
// RIGHT: "age" is interpreted as property | ||
["filter", [18, "<", ["age"]]] | ||
["filter", ["age", ">", 18]] | ||
``` | ||
2. In order to specify a text on the left side of an operator instead of having it interpreted as a property, the `string` function can be used: | ||
```js | ||
// WRONG: "New York" is interpreted as property | ||
["filter", ["New York", "==", ["address", "city"]]] | ||
// RIGHT: "New York" is interpreted as text | ||
["filter", [["string", "New York"], "==", ["address", "city"]]] | ||
["filter", [["address", "city"], "==", "New York"]] | ||
``` | ||
</details> | ||
### Properties and paths | ||
A _property_ is a string pointing to a value inside an object. For example the following property refers to the value of property `age` in an object: | ||
```json | ||
"age" | ||
``` | ||
A _path_ is an array with _properties_. The following path for example describes the value of a nested property `city` inside an object `address`: | ||
```json | ||
["address", "city"] | ||
``` | ||
Note that a path containing a single property is equivalent to just the property itself: | ||
```js | ||
// path ["age"] is equivalent to property "age": | ||
["sort", ["age"]] | ||
["sort", "age"] | ||
``` | ||
<details> | ||
<summary><b>Special cases regarding paths</b></summary> | ||
There is one special case regarding paths: | ||
1. When having a path where the first property is a function name like `["sort"]`, it will be interpreted as a function and not as a path. To parse this as a path, use the function `get`: | ||
```js | ||
const data = { sort: 42 } | ||
jsonquery(data, ["get", "sort"]) // 42 | ||
``` | ||
</details> | ||
### Pipes | ||
A _pipe_ is an array containing a series of _functions_, _operators_, _properties_, _objects_, or _pipes_. The entries in the pipeline are executed one by one, and the output of the first is the input for the next. The following example will first filter the items of an array that have a nested property `city` in the object `address` with the value `"New York"`, and next, sort the filtered items by the property `age`: | ||
A _pipe_ is an array containing a series of _functions_, _objects_, or _pipes_. The entries in the pipeline are executed one by one, and the output of the first is the input for the next. The following example will first filter the items of an array that have a nested property `city` in the object `address` with the value `"New York"`, and next, sort the filtered items by the property `age`: | ||
```json | ||
[ | ||
["filter", [["address", "city"], "==", "New York"]], | ||
["sort", "age"] | ||
["filter", ["eq", ["get" ,"address", "city"], "New York"]], | ||
["sort", ["get" ,"age"]] | ||
] | ||
@@ -285,4 +205,4 @@ ``` | ||
["map", { | ||
"firstName": "name", | ||
"city": ["address", "city"] | ||
"firstName": ["get", "name"], | ||
"city": ["get", "address", "city"] | ||
}] | ||
@@ -295,6 +215,6 @@ ``` | ||
{ | ||
"names": ["map", "name"], | ||
"names": ["map", ["get", "name"]], | ||
"count": ["size"], | ||
"averageAge": [ | ||
["map", "age"], | ||
["map", ["get", "age"]], | ||
["average"] | ||
@@ -331,3 +251,3 @@ ] | ||
If the parameters are not a primitive value but can be a query themselves, the function `compile` can be used to compile them. For example, the actual implementation of the function `filter` is the following: | ||
If the parameters are not a static value but can be a query themselves, the function `compile` can be used to compile them. For example, the actual implementation of the function `filter` is the following: | ||
@@ -348,20 +268,2 @@ ```js | ||
- `operators` is an optional map with custom operator creators. An operator creator receives the left and right side queries as input, and must return a function that implements the operator. Example: | ||
```js | ||
const options = { | ||
operators: { | ||
// a loosely equal operator | ||
// usage example: ["value", "~=", 2] | ||
'~=': (left, right) => { | ||
const a = compile(left) | ||
const b = compile(right) | ||
return (data) => a(data) == b(data) | ||
} | ||
} | ||
} | ||
``` | ||
You can have a look at the source code of the functions in `/src/operators.ts` for more examples. | ||
Here an example of using the function `jsonquery`: | ||
@@ -378,3 +280,3 @@ | ||
const result = jsonquery(data, ["filter", "age", ">", 20]) | ||
const result = jsonquery(data, ["filter", ["gt", ["get", "age"], 20]]) | ||
// result = [ | ||
@@ -399,3 +301,3 @@ // { "name": "Chris", "age": 23 }, | ||
const queryIt = compile(["filter", "age", ">", 20]) | ||
const queryIt = compile(["filter", ["gt", ["get", "age"], 20]]) | ||
@@ -420,12 +322,14 @@ const data = [ | ||
```js | ||
const data = [ | ||
{ "name": "Chris", "age": 23, "scores": [7.2, 5, 8.0] }, | ||
{ "name": "Emily", "age": 19 }, // scores is missing here! | ||
{ "name": "Joe", "age": 32, "scores": [6.1, 8.1] } | ||
] | ||
const data = { | ||
"participants": [ | ||
{ "name": "Chris", "age": 23, "scores": [7.2, 5, 8.0] }, | ||
{ "name": "Emily", "age": 19 }, | ||
{ "name": "Joe", "age": 32, "scores": [6.1, 8.1] } | ||
] | ||
} | ||
try { | ||
jsonquery(data, [ | ||
["pick", "age", "scores"], | ||
["map", ["scores", ["sum"]]] | ||
["get", "participants"], | ||
["map", [["get", "scores"], ["sum"]]] | ||
]) | ||
@@ -437,10 +341,12 @@ } catch (err) { | ||
// { | ||
// "data": [ | ||
// { "name": "Chris", "age": 23, "scores": [7.2, 5, 8.0] }, | ||
// { "name": "Emily", "age": 19 }, | ||
// { "name": "Joe", "age": 32, "scores": [6.1, 8.1] } | ||
// ], | ||
// "data": { | ||
// "participants": [ | ||
// { "name": "Chris", "age": 23, "scores": [7.2, 5, 8.0] }, | ||
// { "name": "Emily", "age": 19 }, | ||
// { "name": "Joe", "age": 32, "scores": [6.1, 8.1] } | ||
// ] | ||
// }, | ||
// "query": [ | ||
// ["pick", "age", "scores"], | ||
// ["map", ["scores", ["sum"]]] | ||
// ["get", "participants"], | ||
// ["map", [["get", "scores"], ["sum"]]] | ||
// ] | ||
@@ -450,11 +356,11 @@ // }, | ||
// "data": [ | ||
// { "age": 23, "scores": [7.2, 5, 8.0] }, | ||
// { "age": 19 }, | ||
// { "age": 32, "scores": [6.1, 8.1] } | ||
// { "name": "Chris", "age": 23, "scores": [7.2, 5, 8.0] }, | ||
// { "name": "Emily", "age": 19 }, | ||
// { "name": "Joe", "age": 32, "scores": [6.1, 8.1] } | ||
// ], | ||
// "query": ["map", ["scores", ["sum"]]] | ||
// "query": ["map", [["get", "scores"], ["sum"]]] | ||
// }, | ||
// { | ||
// "data": {"age": 19}, | ||
// "query": ["scores", ["sum"]] | ||
// "data": { "name": "Emily", "age": 19 }, | ||
// "query": [["get", "scores"], ["sum"]] | ||
// }, | ||
@@ -469,77 +375,21 @@ // { | ||
## Limitations | ||
## Gotchas | ||
The JSON Query language has some limitations, pitfalls, and gotchas. | ||
The JSON Query language has some gotchas. What can be confusing at first is to understand how data is piped through the query. A traditional function call is for example `max(myValues)`, so you may expect to have to write this in JSON Query like `["max", "myValues"]`. However, JSON Query has a functional approach where we create a pipeline like: `data -> max -> result`. So, you will have to write a pipe first getting this property and then calling abs: `[["get", "myValues"], ["max"]]"`. | ||
Though the language is easy to learn and understand, it is relatively verbose due to the need for quotes around all keys, and the need for a lot of arrays in square brackets `[...]`. This is a consequence of expressing a query using JSON whilst wanting to keep the language concise. | ||
It's easy to forget to specify a property getter and instead, just specify a string with the property name, like: | ||
The use of arrays `[...]` is quite overloaded. An array can hold a function call, operator, pipe, or path with properties. Given a query being an array containing three strings `[string, string, string]` for example, it's meaning can only be determined by looking up whether the first string matches a known function, then looking up whether the second string matches a known operator, and lastly conclude that it is a path with properties. When making a mistake, the error message you get is mostly unhelpful, and the best way to debug is to build your query step by step, validating that it works after every step. | ||
```js | ||
const data = [ | ||
{"name": "Chris", "age": 23, "city": "New York"}, | ||
{"name": "Emily", "age": 19, "city": "Atlanta"}, | ||
{"name": "Joe", "age": 16, "city": "New York"} | ||
] | ||
What can also be confusing at first is to understand how data is piped through the query. A traditional function call is for example `abs(myValue)`, so you may expect to have to write this in JSON Query like `["abs", "myValue"]`. However, JSON Query has a functional approach where we create a pipeline like: `data -> abs -> result`. So, to get the absolute value of a property `myValue`, you will have to write a pipe first getting this property and then calling abs: `[["get", "myValue"], ["abs"]]"`. | ||
const result = jsonquery(data, ["filter", ["eq", "city", "New York"]]) | ||
// result: empty array | ||
// expecteed: an array with two items | ||
// solution: specify "city" as a getter like ["filter", ["eq", ["get" "city"], "New York"]] | ||
``` | ||
### Gotchas | ||
Here some gotchas. | ||
1. Having an problem halfway the query, resulting in a vague error. In the following example, the first part of the query results in `undefined`, and then we try to filter that, resulting in an error: | ||
```js | ||
const data = { | ||
"friends": [ | ||
{"name": "Chris", "age": 23, "city": "New York"}, | ||
{"name": "Emily", "age": 19, "city": "Atlanta"}, | ||
{"name": "Joe", "age": 16, "city": "New York"} | ||
] | ||
} | ||
const result = jsonquery(data, [ | ||
["get", "friiends"], | ||
["filter", ["city", "==", "New York"]] | ||
]) | ||
// result: "Error: e is undefined" | ||
// expected: an array with two items | ||
``` | ||
2. Making a typo in a function name, which then is interpreted as getting a property. This results in vague output or in an error. In the following example, the property `"filte"` is read from the data, resulting in `undefined`. After that, the property `"city"` is read from `undefined`, resulting in `undefined`, and lastly, we check whether `undefined` is equal to the string `"New York"`, which is not the case, so, the query returns `false`. | ||
```js | ||
const data = [ | ||
{"name": "Chris", "age": 23, "city": "New York"}, | ||
{"name": "Emily", "age": 19, "city": "Atlanta"}, | ||
{"name": "Joe", "age": 16, "city": "New York"} | ||
] | ||
const result = jsonquery(data, ["filte", ["city", "==", "New York"]]) | ||
// result: the boolean value false | ||
// expected: an array with two items | ||
``` | ||
3. Making a typo in a property name, resulting in unexpected results. | ||
```js | ||
const data = [ | ||
{"name": "Chris", "age": 23, "city": "New York"}, | ||
{"name": "Emily", "age": 19, "city": "Atlanta"}, | ||
{"name": "Joe", "age": 16, "city": "New York"} | ||
] | ||
const result = jsonquery(data, ["filter", ["cities", "==", "New York"]]) | ||
// result: an empty array | ||
// expected: an array with two items | ||
``` | ||
4. Forgetting brackets around a nested query. In the following example, the filter condition has no brackets. Therefore, the property `"city"` is used as condition and the arguments `"=="` and `"New York"` are ignored. | ||
```js | ||
const data = [ | ||
{"name": "Chris", "age": 23, "city": "New York"}, | ||
{"name": "Emily", "age": 19, "city": "Atlanta"}, | ||
{"name": "Joe", "age": 16, "city": "New York"} | ||
] | ||
const result = jsonquery(data, ["filter", "age", ">", 18]) | ||
// result: the original data | ||
// expected: an array with two items | ||
``` | ||
## Development | ||
@@ -576,2 +426,6 @@ | ||
4. **Parsable** | ||
When a query language is simple to parse, it is easy to write integrations and adapters for it. For example, it is possible to write a visual user interface to write queries, and the query language can be implemented in various environments (frontend, backend). | ||
The `jsonquery` language is inspired by [JavaScript+Lodash](https://jsoneditoronline.org/indepth/query/10-best-json-query-languages/#javascript), [JSON Patch](https://jsonpatch.com/), and [MongoDB aggregates](https://www.mongodb.com/docs/manual/aggregation/). It is basically a JSON notation to describe making a series of function calls. It has no magic syntax except for the need to be familiar with JSON, making it flexible and easy to understand. The library is extremely small thanks to smartly utilizing built-in JavaScript functions and the built-in JSON parser, requiring very little code to make the query language work. | ||
@@ -578,0 +432,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
40332
184
415
1