Skip to content

Commit 7590dc0

Browse files
committed
feat: introduce static query method
1 parent 42f7d3e commit 7590dc0

File tree

6 files changed

+142
-233
lines changed

6 files changed

+142
-233
lines changed

README.md

+118-219
Original file line numberDiff line numberDiff line change
@@ -33,241 +33,140 @@
3333

3434
## Usage
3535

36-
```js
37-
import Nimma from 'https://cdn.skypack.dev/nimma';
38-
39-
const n = new Nimma([
40-
'$.info',
41-
'$.info.contact',
42-
'$.info^',
43-
'$.info^~',
44-
'$.servers[*].url',
45-
'$.servers[0:2]',
46-
'$.servers[:5]',
47-
"$.bar['children']",
48-
"$.bar['0']",
49-
"$.bar['children.bar']",
50-
'$.channels[*][publish,subscribe][?(@.schemaFormat === void 0)].payload',
51-
"$..[?( @property === 'get' || @property === 'put' || @property === 'post' )]",
52-
"$..paths..[?( @property === 'get' || @property === 'put' || @property === 'post' )]",
53-
`$.examples.*`,
54-
'$[1:-5:-2]',
55-
'$..foo..[?( @property >= 900 )]..foo',
56-
]);
57-
58-
// you can perform the query...
59-
n.query(document, {
60-
['$.info']({ value, path }) {
61-
//
62-
},
63-
// and so on for each specified path
64-
});
65-
66-
// ... or write the generated code. It's advisable to write the code to further re-use.
67-
await cache.writeFile('./nimma-code.mjs', n.sourceCode); // once
68-
```
69-
70-
Here's how the sourceCode would look like for the above path expressions
36+
### Querying
7137

7238
```js
73-
import { Scope, isObject, inBounds } from 'nimma/runtime';
74-
const tree = {
75-
'$.info': function (scope, fn) {
76-
const value = scope.sandbox.root;
77-
if (isObject(value)) {
78-
scope.fork(['info'])?.emit(fn, 0, false);
39+
import Nimma from 'nimma';
40+
41+
const document = {
42+
info: {
43+
title: 'Example API',
44+
version: '1.0.0',
45+
contact: {
46+
name: 'API Support',
47+
url: 'http://www.example.com/support',
48+
email: ''
7949
}
8050
},
81-
'$.info.contact': function (scope, fn) {
82-
const value = scope.sandbox.root?.['info'];
83-
if (isObject(value)) {
84-
scope.fork(['info', 'contact'])?.emit(fn, 0, false);
85-
}
86-
},
87-
'$.info^': function (scope, fn) {
88-
const value = scope.sandbox.root;
89-
if (isObject(value)) {
90-
scope.fork(['info'])?.emit(fn, 1, false);
91-
}
92-
},
93-
'$.info^~': function (scope, fn) {
94-
const value = scope.sandbox.root;
95-
if (isObject(value)) {
96-
scope.fork(['info'])?.emit(fn, 1, true);
97-
}
98-
},
99-
'$.servers[*].url': function (scope, fn) {
100-
if (scope.depth !== 2) return;
101-
if (scope.path[0] !== 'servers') return;
102-
if (scope.path[2] !== 'url') return;
103-
scope.emit(fn, 0, false);
104-
},
105-
'$.servers[0:2]': function (scope, fn) {
106-
if (scope.depth !== 1) return;
107-
if (scope.path[0] !== 'servers') return;
108-
if (typeof scope.path[1] !== 'number' || scope.path[1] >= 2) return;
109-
scope.emit(fn, 0, false);
110-
},
111-
'$.servers[:5]': function (scope, fn) {
112-
if (scope.depth !== 1) return;
113-
if (scope.path[0] !== 'servers') return;
114-
if (typeof scope.path[1] !== 'number' || scope.path[1] >= 5) return;
115-
scope.emit(fn, 0, false);
116-
},
117-
"$.bar['children']": function (scope, fn) {
118-
const value = scope.sandbox.root?.['bar'];
119-
if (isObject(value)) {
120-
scope.fork(['bar', 'children'])?.emit(fn, 0, false);
51+
paths: {
52+
'/users': {
53+
get: {
54+
summary: 'Returns a list of users.',
55+
operationId: 'getUsers',
56+
responses: {
57+
'200': {
58+
description: 'OK',
59+
}
60+
}
61+
},
62+
post: {
63+
summary: 'Creates a new user.',
64+
operationId: 'createUser',
65+
responses: {
66+
'200': {
67+
description: 'OK',
68+
}
69+
}
70+
},
71+
put: {
72+
summary: 'Updates a user.',
73+
operationId: 'updateUser',
74+
responses: {
75+
'200': {
76+
description: 'OK',
77+
}
78+
}
79+
}
12180
}
81+
}
82+
};
83+
84+
const query = Nimma.query(document, {
85+
'$.info'({ path, value }) {
86+
console.log(path, value);
12287
},
123-
"$.bar['0']": function (scope, fn) {
124-
const value = scope.sandbox.root?.['bar'];
125-
if (isObject(value)) {
126-
scope.fork(['bar', '0'])?.emit(fn, 0, false);
127-
}
88+
'$.info.contact'({ path, value }) {
89+
console.log(path, value);
12890
},
129-
"$.bar['children.bar']": function (scope, fn) {
130-
const value = scope.sandbox.root?.['bar'];
131-
if (isObject(value)) {
132-
scope.fork(['bar', 'children.bar'])?.emit(fn, 0, false);
91+
'$.paths[*][get,post]'({ path, value }) {
92+
console.log(path, value);
93+
}
94+
});
95+
96+
// a given instance can be re-used to traverse another document
97+
query({
98+
info: {
99+
title: 'Example API',
100+
version: '2.0.0',
101+
contact: {
102+
email: ''
133103
}
134104
},
135-
'$.channels[*][publish,subscribe][?(@.schemaFormat === void 0)].payload':
136-
function (scope, fn) {
137-
if (scope.depth !== 4) return;
138-
if (scope.path[0] !== 'channels') return;
139-
if (scope.path[2] !== 'publish' && scope.path[2] !== 'subscribe') return;
140-
if (!(scope.sandbox.at(3).value.schemaFormat === void 0)) return;
141-
if (scope.path[4] !== 'payload') return;
142-
scope.emit(fn, 0, false);
143-
},
144-
"$..[?( @property === 'get' || @property === 'put' || @property === 'post' )]":
145-
function (scope, fn) {
146-
if (
147-
!(
148-
scope.sandbox.property === 'get' ||
149-
scope.sandbox.property === 'put' ||
150-
scope.sandbox.property === 'post'
151-
)
152-
)
153-
return;
154-
scope.emit(fn, 0, false);
155-
},
156-
"$..paths..[?( @property === 'get' || @property === 'put' || @property === 'post' )]":
157-
function (scope, fn) {
158-
if (scope.depth < 1) return;
159-
let pos = 0;
160-
if (((pos = scope.path.indexOf('paths', pos)), pos === -1)) return;
161-
if (
162-
scope.depth < pos + 1 ||
163-
((pos = !(
164-
scope.sandbox.property === 'get' ||
165-
scope.sandbox.property === 'put' ||
166-
scope.sandbox.property === 'post'
167-
)
168-
? -1
169-
: scope.depth),
170-
pos === -1)
171-
)
172-
return;
173-
if (scope.depth !== pos) return;
174-
scope.emit(fn, 0, false);
175-
},
176-
'$.examples.*': function (scope, fn) {
177-
if (scope.depth !== 1) return;
178-
if (scope.path[0] !== 'examples') return;
179-
scope.emit(fn, 0, false);
180-
},
181-
'$[1:-5:-2]': function (scope, fn) {
182-
if (scope.depth !== 0) return;
183-
if (
184-
typeof scope.path[0] !== 'number' ||
185-
!inBounds(scope.sandbox.parentValue, scope.path[0], 1, -5, -2)
186-
)
187-
return;
188-
scope.emit(fn, 0, false);
105+
});
106+
```
107+
108+
### Code Generation
109+
110+
Nimma can also generate a JS code that can be used to traverse a given JSON document.
111+
112+
```js
113+
import Nimma from 'nimma';
114+
import * as fs from 'node:fs/promises';
115+
116+
const nimma = new Nimma([
117+
'$.info',
118+
'$.info.contact',
119+
'$.servers[:5]',
120+
'$.paths[*][*]'
121+
], {
122+
module: 'esm' // or 'cjs' for CommonJS. 'esm' is the default value
123+
});
124+
125+
// for esm
126+
await fs.writeFile('./nimma-code.mjs', nimma.sourceCode);
127+
128+
// for cjs
129+
await fs.writeFile('./nimma-code.cjs', nimma.sourceCode);
130+
131+
// you can also use the code directly
132+
nimma.query(document, {
133+
// you need to provide a callback for each JSON Path expression
134+
'$.info'({ path, value }) {
135+
console.log(path, value);
189136
},
190-
'$..foo..[?( @property >= 900 )]..foo': function (scope, fn) {
191-
scope.bail(
192-
'$..foo..[?( @property >= 900 )]..foo',
193-
scope => scope.emit(fn, 0, false),
194-
[
195-
{
196-
fn: scope => scope.property !== 'foo',
197-
deep: true,
198-
},
199-
{
200-
fn: scope => !(scope.sandbox.property >= 900),
201-
deep: true,
202-
},
203-
{
204-
fn: scope => scope.property !== 'foo',
205-
deep: true,
206-
},
207-
],
208-
);
137+
'$.info.contact'({ path, value }) {
138+
console.log(path, value);
139+
},
140+
'$.servers[:5]'({ path, value }) {
141+
console.log(path, value);
209142
},
210-
};
211-
export default function (input, callbacks) {
212-
const scope = new Scope(input);
213-
const _tree = scope.registerTree(tree);
214-
const _callbacks = scope.proxyCallbacks(callbacks, {});
215-
try {
216-
_tree['$.info'](scope, _callbacks['$.info']);
217-
_tree['$.info.contact'](scope, _callbacks['$.info.contact']);
218-
_tree['$.info^'](scope, _callbacks['$.info^']);
219-
_tree['$.info^~'](scope, _callbacks['$.info^~']);
220-
_tree["$.bar['children']"](scope, _callbacks["$.bar['children']"]);
221-
_tree["$.bar['0']"](scope, _callbacks["$.bar['0']"]);
222-
_tree["$.bar['children.bar']"](scope, _callbacks["$.bar['children.bar']"]);
223-
_tree['$..foo..[?( @property >= 900 )]..foo'](
224-
scope,
225-
_callbacks['$..foo..[?( @property >= 900 )]..foo'],
226-
);
227-
scope.traverse(() => {
228-
_tree['$.servers[*].url'](scope, _callbacks['$.servers[*].url']);
229-
_tree['$.servers[0:2]'](scope, _callbacks['$.servers[0:2]']);
230-
_tree['$.servers[:5]'](scope, _callbacks['$.servers[:5]']);
231-
_tree[
232-
'$.channels[*][publish,subscribe][?(@.schemaFormat === void 0)].payload'
233-
](
234-
scope,
235-
_callbacks[
236-
'$.channels[*][publish,subscribe][?(@.schemaFormat === void 0)].payload'
237-
],
238-
);
239-
_tree[
240-
"$..[?( @property === 'get' || @property === 'put' || @property === 'post' )]"
241-
](
242-
scope,
243-
_callbacks[
244-
"$..[?( @property === 'get' || @property === 'put' || @property === 'post' )]"
245-
],
246-
);
247-
_tree[
248-
"$..paths..[?( @property === 'get' || @property === 'put' || @property === 'post' )]"
249-
](
250-
scope,
251-
_callbacks[
252-
"$..paths..[?( @property === 'get' || @property === 'put' || @property === 'post' )]"
253-
],
254-
);
255-
_tree['$.examples.*'](scope, _callbacks['$.examples.*']);
256-
_tree['$[1:-5:-2]'](scope, _callbacks['$[1:-5:-2]']);
257-
});
258-
} finally {
259-
scope.destroy();
143+
'$.paths[*][*]'({ path, value }) {
144+
console.log(path, value);
260145
}
261-
}
146+
});
262147
```
263148

264-
Since it's a valid ES Module, you can easily load it again and there's no need for `new Nimma`.
149+
Once the code is written to the file, you can use it as follows:
265150

266-
### Supported opts
151+
```js
152+
import query from './nimma-code.mjs'; // or const query = require('./nimma-code.cjs');
267153

268-
- output: ES2018 | ES2021 | auto
269-
- fallback
270-
- unsafe
154+
query(document, {
155+
// you need to provide a callback for each JSON Path expression
156+
'$.info'({ path, value }) {
157+
console.log(path, value);
158+
},
159+
'$.info.contact'({ path, value }) {
160+
console.log(path, value);
161+
},
162+
'$.servers[:5]'({ path, value }) {
163+
console.log(path, value);
164+
},
165+
'$.paths[*][*]'({ path, value }) {
166+
console.log(path, value);
167+
}
168+
});
169+
```
271170

272171
## Comparison vs jsonpath-plus and alikes
273172

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nimma",
3-
"version": "0.3.1",
3+
"version": "0.4.0",
44
"description": "Scalable JSONPath engine.",
55
"keywords": [
66
"json",
@@ -57,7 +57,7 @@
5757
"build": "export NODE_ENV=production; rollup -c",
5858
"lint": "ls-lint && eslint --cache --cache-location .cache/ src && prettier --ignore-path .gitignore --check --cache --cache-location .cache/.prettier src",
5959
"test": "export NODE_ENV=test; c8 mocha --config .mocharc ./**/__tests__/**/*.test.mjs && karma start karma.conf.cjs",
60-
"prepublishOnly": "npm run lint && npm run test && npm run build && (stat ./dist/esm/parser/parser.mjs && stat ./dist/cjs/parser/parser.js) >> /dev/null"
60+
"prepublishOnly": "npm run lint && npm run test && npm run build"
6161
},
6262
"devDependencies": {
6363
"@babel/core": "^7.23.9",

0 commit comments

Comments
 (0)