Created
April 22, 2019 03:46
-
-
Save ancms2600/58d339f6f991bb3dae9d20f106440d9e to your computer and use it in GitHub Desktop.
jq implemented in NodeJS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const vm = require('vm'); | |
const fp = require('lodash/fp'); | |
const _ = require('lodash'); | |
/** | |
* Successfully mimics jq C implementation in pure Node.JS | |
* by utilizing the Functional Programming (FP) version of Lodash library, which is | |
* critical for jq-like syntax. | |
* | |
* Documentation: https://github.com/lodash/lodash/wiki/FP-Guide | |
* | |
* NOTICE: The node vm module is sandboxed, but could still be abused. | |
* Validation of user input is critical prior to using this feature! | |
* | |
* @param {mixed} data - Input to operate on | |
* @param {string} query - Line of code to execute (after converting pipes) | |
* @param {Object} opts - Various options which you can pass to affect the output. | |
* @returns {mixed} Output | |
*/ | |
exports.jq = (data, query, opts) => { | |
// TODO: provide client-side version? it would have to rely on eval()... | |
try { | |
const sandbox = { | |
// lodash dependency; flattened into namespace. | |
// whitelisted to most limited scope possible to limit exploitation and chicanery. | |
..._.pick(fp, 'flow thru map get getOr'.split(/ /g)), | |
// any provided scope | |
..._.get(opts, 'scope', {}), | |
// input data, as a magic value | |
__INPUT__: data, | |
}; | |
const js = query | |
.replace(/^\.$/, 'identity') | |
.replace(/ \| /g, ','); | |
const src = `flow(${js})(__INPUT__);`; | |
const scriptInst = new vm.Script(src); | |
const ctx = vm.createContext(sandbox); | |
const output = scriptInst.runInContext(ctx); | |
return output; | |
} | |
catch (e) { | |
throw Error(`Invalid expression! `+ (e instanceof Error ? e.message : ''+ e)); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { assert } = require('chai'); | |
const { jq } = require('../../../../shared/utils/jq'); | |
// optional example user-provided dependency | |
const Sugar = require('sugar'); | |
describe('JQ Node', () => { | |
it('can mimic the basic example from the docs', async () => { | |
const result = jq( | |
'October 31, 2011', | |
`thru(a => Sugar.Date.create(a).toISOString())`, { | |
// NOTICE: permits the use of third-party libs being in-scope | |
scope: { Sugar }, | |
}); | |
assert.equal(result, '2011-10-31T06:00:00.000Z'); | |
}); | |
it('can operate on object results', async () => { | |
const INPUT_FIXTURE = require('/tmp/example.json'); | |
const result = jq(INPUT_FIXTURE, | |
//.[]._source.jobResult.reportData.a.b.c | |
`map('_source.jobResult.reportData.a.b.c')`); | |
assert.sameOrderedMembers(result, [undefined, undefined, '22.22.33.12']); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment