Skip to content

Commit 958a31e

Browse files
committed
feat: implement functions reverse, mapObject, mapKeys, mapValues, join, split, number, string
1 parent 615393a commit 958a31e

File tree

6 files changed

+516
-9
lines changed

6 files changed

+516
-9
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ Try it out on the online playground: <https://jsonquerylang.org>
1010

1111
## Features
1212

13-
- Small: just `3.0 kB` when minified and gzipped! The JSON query engine without parse/stringify is only `1.4 kB`.
14-
- Feature rich (40+ powerful functions)
13+
- Small: just `3.2 kB` when minified and gzipped! The JSON query engine without parse/stringify is only `1.7 kB`.
14+
- Feature rich (50+ powerful functions and operators)
1515
- Easy to interoperate with thanks to the intermediate JSON format.
1616
- Expressive
1717
- Expandable

docs/index.html

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,29 @@ <h1>JSON Query</h1>
115115
<a href="https://github.com/jsonquerylang/jsonquery/blob/main/reference/functions.md#sort"
116116
target="_blank"><code>sort(property, direction)</code></a>
117117
</li>
118+
<li>
119+
<a href="https://github.com/jsonquerylang/jsonquery/blob/main/reference/functions.md#reverse"
120+
target="_blank"><code>reverse()</code></a>
121+
</li>
118122
<li>
119123
<a href="https://github.com/jsonquerylang/jsonquery/blob/main/reference/functions.md#pick"
120124
target="_blank"><code>pick(property1, property2, ...)</code></a>
121125
</li>
122126
<li>
123127
<a href="https://github.com/jsonquerylang/jsonquery/blob/main/reference/functions.md#map"
124-
target="_blank"><code>map(query)</code></a>
128+
target="_blank"><code>map(callback)</code></a>
129+
</li>
130+
<li>
131+
<a href="https://github.com/jsonquerylang/jsonquery/blob/main/reference/functions.md#mapObject"
132+
target="_blank"><code>mapObject(callback)</code></a>
133+
</li>
134+
<li>
135+
<a href="https://github.com/jsonquerylang/jsonquery/blob/main/reference/functions.md#mapKeys"
136+
target="_blank"><code>mapKeys(callback)</code></a>
137+
</li>
138+
<li>
139+
<a href="https://github.com/jsonquerylang/jsonquery/blob/main/reference/functions.md#mapValues"
140+
target="_blank"><code>mapValues(callback)</code></a>
125141
</li>
126142
<li>
127143
<a href="https://github.com/jsonquerylang/jsonquery/blob/main/reference/functions.md#pipe"
@@ -155,6 +171,14 @@ <h1>JSON Query</h1>
155171
<a href="https://github.com/jsonquerylang/jsonquery/blob/main/reference/functions.md#flatten"
156172
target="_blank"><code>flatten()</code></a>
157173
</li>
174+
<li>
175+
<a href="https://github.com/jsonquerylang/jsonquery/blob/main/reference/functions.md#join"
176+
target="_blank"><code>join(separator)</code></a>
177+
</li>
178+
<li>
179+
<a href="https://github.com/jsonquerylang/jsonquery/blob/main/reference/functions.md#split"
180+
target="_blank"><code>split(separator)</code></a>
181+
</li>
158182
<li>
159183
<a href="https://github.com/jsonquerylang/jsonquery/blob/main/reference/functions.md#uniq"
160184
target="_blank"><code>uniq()</code></a>
@@ -218,6 +242,14 @@ <h1>JSON Query</h1>
218242
<a href="https://github.com/jsonquerylang/jsonquery/blob/main/reference/functions.md#abs"
219243
target="_blank"><code>abs(value)</code></a>
220244
</li>
245+
<li>
246+
<a href="https://github.com/jsonquerylang/jsonquery/blob/main/reference/functions.md#number"
247+
target="_blank"><code>number(text)</code></a>
248+
</li>
249+
<li>
250+
<a href="https://github.com/jsonquerylang/jsonquery/blob/main/reference/functions.md#string"
251+
target="_blank"><code>string(number)</code></a>
252+
</li>
221253
</ul>
222254
</div>
223255
</details>

reference/functions.md

Lines changed: 151 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,22 @@ jsonquery(values, 'sort()') // [2, 7, 9]
217217
jsonquery(values, 'sort(get(), "desc")') // [9, 7, 2]
218218
```
219219

220+
## reverse
221+
222+
Create a new array with the items in reverse order.
223+
224+
```text
225+
reverse()
226+
```
227+
228+
Example:
229+
230+
```js
231+
const data = [1, 2, 3]
232+
jsonquery(data, 'reverse()')
233+
// [3, 2, 1]
234+
```
235+
220236
## pick
221237

222238
Pick one or multiple properties or paths, and create a new, flat object for each of them. Can be used on both an object or an array.
@@ -255,10 +271,10 @@ jsonquery(item, 'pick(.price)') // 25
255271

256272
## map
257273

258-
Map over an array and apply the provided query to each of the items in the array.
274+
Map over an array and apply the provided callback query to each of the items in the array.
259275

260276
```text
261-
map(query)
277+
map(callback)
262278
```
263279

264280
Examples:
@@ -288,6 +304,60 @@ jsonquery(data, 'map(.price * .quantity)')
288304
// 8.6
289305
```
290306

307+
## mapObject
308+
309+
Map over an object, and create a new object with the entry `{ key, value }` returned by the callback for every input entry.
310+
311+
```text
312+
mapObject(callback)
313+
```
314+
315+
Example:
316+
317+
```js
318+
const data = { "a": 2, "b": 3 }
319+
jsonquery(data, `mapObject({
320+
key: (.key + " times two"),
321+
value: (.value * 2)
322+
})`)
323+
// {
324+
// "a times two": 4,
325+
// "b times two": 6
326+
// }
327+
```
328+
329+
## mapKeys
330+
331+
Map over an object, and create a new object with the keys returned by the callback having the value of the original key.
332+
333+
```text
334+
mapKeys(callback)
335+
```
336+
337+
Example:
338+
339+
```js
340+
const data = { "a": 2, "b": 3 }
341+
jsonquery(data, 'mapKeys("#" + get())')
342+
// { "#a": 2, "#b": 3 }
343+
```
344+
345+
## mapValues
346+
347+
Map over an object, and create a new object with the values updated by the return value of callback.
348+
349+
```text
350+
mapValues(callback)
351+
```
352+
353+
Example:
354+
355+
```js
356+
const data = { "a": 2, "b": 3 }
357+
jsonquery(data, 'mapValues(get() * 2)')
358+
// { "a": 4, "b": 6 }
359+
```
360+
291361
## groupBy
292362

293363
Group a list with objects grouped by the value of given path. This creates an object with the different properties as key, and an array with all items having that property as value.
@@ -416,6 +486,45 @@ const data2 = [[1, 2, [3, 4]]]
416486
jsonquery(data2, 'flatten()') // [1, 2, [3, 4]]
417487
```
418488

489+
## join
490+
491+
Concatenate array items into a string with an optional separator.
492+
493+
```text
494+
join()
495+
join(separator)
496+
```
497+
498+
Example:
499+
500+
```js
501+
const data = [
502+
{ "name": "Chris", "age": 16 },
503+
{ "name": "Emily", "age": 32 },
504+
{ "name": "Joe", "age": 18 }
505+
]
506+
507+
jsonquery(data, 'map(.name) | join(", ")')
508+
// "Chris, Emily, Joe"
509+
```
510+
511+
## split
512+
513+
Divide a string into an array substrings, separated by a separator.
514+
515+
```text
516+
split()
517+
split(separator)
518+
```
519+
520+
Example:
521+
522+
```js
523+
const data = "hi there how are you doing?"
524+
jsonquery(data, 'split(" ")')
525+
// ["hi", "there", "how", "are", "you", "doing?"]
526+
```
527+
419528
## uniq
420529

421530
Create a copy of an array where all duplicates are removed.
@@ -479,7 +588,7 @@ jsonquery(data, 'limit(4)') // [1, 2, 3, 4]
479588

480589
## size
481590

482-
Return the size of an array.
591+
Return the size of an array or the length of a string.
483592

484593
```text
485594
size()
@@ -490,6 +599,7 @@ Examples:
490599
```js
491600
jsonquery([1, 2], 'size()') // 2
492601
jsonquery([1, 2, 3, 4], 'size()') // 4
602+
jsonquery("hello", 'size()') // 5
493603
```
494604

495605
## sum
@@ -951,6 +1061,13 @@ Examples:
9511061
const data = { "a": 6, "b": 2 }
9521062

9531063
jsonquery(data, '.a + .b') // 8
1064+
1065+
const user = {
1066+
"firstName": "José",
1067+
"lastName": "Carioca"
1068+
}
1069+
jsonquery(user, '(.firstName + " ") + .lastName')
1070+
// "José Carioca"
9541071
```
9551072

9561073
## subtract (`-`)
@@ -1069,3 +1186,34 @@ jsonquery({"a": 23.1345 }, 'round(.a)') // 23
10691186
jsonquery({"a": 23.1345 }, 'round(.a, 2)') // 23.13
10701187
jsonquery({"a": 23.1345 }, 'round(.a, 3)') // 23.135
10711188
```
1189+
1190+
## number
1191+
1192+
Parse the numeric value in a string into a number.
1193+
1194+
```text
1195+
number(text)
1196+
```
1197+
1198+
Examples:
1199+
1200+
```js
1201+
jsonquery({"value": "2.4" }, 'number(.value)') // 2.4
1202+
jsonquery("-4e3", 'number(get())') // -4000
1203+
jsonquery("2,7,1", 'split(",") | map(number(get()))') // [2, 7, 1]
1204+
```
1205+
1206+
## string
1207+
1208+
Format a number as a string.
1209+
1210+
```text
1211+
string(number)
1212+
```
1213+
1214+
Examples:
1215+
1216+
```js
1217+
jsonquery({"value": 2.4 }, 'string(.value)') // "2.4"
1218+
jsonquery(42, 'string(get())') // "42"
1219+
```

src/functions.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { compile } from './compile'
22
import { isArray } from './is'
33
import type {
4+
Entry,
45
FunctionBuilder,
56
FunctionBuildersMap,
67
Getter,
@@ -78,6 +79,44 @@ export const functions: FunctionBuildersMap = {
7879
return (data: T[]) => data.map(_callback)
7980
},
8081

82+
mapObject: <T, U>(callback: JSONQuery) => {
83+
const _callback = compile(callback)
84+
85+
return (data: Record<string, T>) => {
86+
const output = {}
87+
for (const key of Object.keys(data)) {
88+
const updated = _callback({ key, value: data[key] }) as Entry<U>
89+
output[updated.key] = updated.value
90+
}
91+
return output
92+
}
93+
},
94+
95+
mapKeys: <T>(callback: JSONQuery) => {
96+
const _callback = compile(callback)
97+
98+
return (data: Record<string, T>) => {
99+
const output = {}
100+
for (const key of Object.keys(data)) {
101+
const updatedKey = _callback(key) as string
102+
output[updatedKey] = data[key]
103+
}
104+
return output
105+
}
106+
},
107+
108+
mapValues: <T>(callback: JSONQuery) => {
109+
const _callback = compile(callback)
110+
111+
return (data: Record<string, T>) => {
112+
const output = {}
113+
for (const key of Object.keys(data)) {
114+
output[key] = _callback(data[key])
115+
}
116+
return output
117+
}
118+
},
119+
81120
filter: <T>(predicate: JSONQuery[]) => {
82121
const _predicate = compile(predicate)
83122

@@ -97,6 +136,11 @@ export const functions: FunctionBuildersMap = {
97136
return (data: T[]) => data.slice().sort(compare)
98137
},
99138

139+
reverse:
140+
<T>() =>
141+
(data: T[]) =>
142+
data.toReversed(),
143+
100144
pick: (...properties: JSONQueryProperty[]) => {
101145
const getters = properties.map(
102146
([_get, ...path]) => [path[path.length - 1], functions.get(...path)] as Getter
@@ -157,6 +201,18 @@ export const functions: FunctionBuildersMap = {
157201

158202
flatten: () => (data: unknown[]) => data.flat(),
159203

204+
join:
205+
<T>(separator = '') =>
206+
(data: T[]) =>
207+
data.join(separator),
208+
209+
split:
210+
(separator = '') =>
211+
(data: string) =>
212+
data.split(separator as string),
213+
214+
substring: (start: number, end: number) => (data: string) => data.slice(Math.max(start, 0), end),
215+
160216
uniq:
161217
() =>
162218
<T>(data: T[]) => [...new Set(data)],
@@ -246,7 +302,13 @@ export const functions: FunctionBuildersMap = {
246302
round: buildFunction((value: number, digits = 0) => {
247303
const num = Math.round(Number(`${value}e${digits}`))
248304
return Number(`${num}e${-digits}`)
249-
})
305+
}),
306+
307+
number: buildFunction((text: string) => {
308+
const num = Number(text)
309+
return Number.isNaN(Number(text)) ? null : num
310+
}),
311+
string: buildFunction(String)
250312
}
251313

252314
const truthy = (x: unknown) => x !== null && x !== 0 && x !== false

src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,8 @@ export type Fun = (data: unknown) => unknown
3232
export type FunctionBuilder = (...args: JSONQuery[]) => Fun
3333
export type FunctionBuildersMap = Record<string, FunctionBuilder>
3434
export type Getter = [key: string, Fun]
35+
36+
export interface Entry<T> {
37+
key: string
38+
value: T
39+
}

0 commit comments

Comments
 (0)