|
33 | 33 |
|
34 | 34 | ## Usage
|
35 | 35 |
|
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 |
71 | 37 |
|
72 | 38 | ```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: '' |
79 | 49 | }
|
80 | 50 | },
|
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 | + } |
121 | 80 | }
|
| 81 | + } |
| 82 | +}; |
| 83 | + |
| 84 | +const query = Nimma.query(document, { |
| 85 | + '$.info'({ path, value }) { |
| 86 | + console.log(path, value); |
122 | 87 | },
|
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); |
128 | 90 | },
|
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: '' |
133 | 103 | }
|
134 | 104 | },
|
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); |
189 | 136 | },
|
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); |
209 | 142 | },
|
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); |
260 | 145 | }
|
261 |
| -} |
| 146 | +}); |
262 | 147 | ```
|
263 | 148 |
|
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: |
265 | 150 |
|
266 |
| -### Supported opts |
| 151 | +```js |
| 152 | +import query from './nimma-code.mjs'; // or const query = require('./nimma-code.cjs'); |
267 | 153 |
|
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 | +``` |
271 | 170 |
|
272 | 171 | ## Comparison vs jsonpath-plus and alikes
|
273 | 172 |
|
|
0 commit comments