Skip to content

Instantly share code, notes, and snippets.

@ancms2600
Created April 22, 2019 03:46
Show Gist options
  • Save ancms2600/58d339f6f991bb3dae9d20f106440d9e to your computer and use it in GitHub Desktop.
Save ancms2600/58d339f6f991bb3dae9d20f106440d9e to your computer and use it in GitHub Desktop.
jq implemented in NodeJS
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));
}
};
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