json-q
Version:
Retrieves values from JSON objects (and JavaScript objects) by css-selector-like query (includes attribute filters and array flattening).
168 lines (147 loc) • 4.73 kB
JavaScript
const clone = require('clone');
const { dedup, deep_filter, flatten } = require('./helper');
const { parse } = require('./parse');
const { parse_filter } = require('./parse_filter');
const { operator } = require('./filter_operators');
const { pseudo } = require('./filter_pseudos');
const get = (obj, path, opt) => {
const ret = _get(clone(obj), parse(path), opt);
return ret;
}
const _get = (obj, flow, opt) => {
flow = Object.assign([], flow);
let ret = [];
if (obj instanceof Array) {
obj.forEach(_itm => {
ret = ret.concat(_get(_itm, flow, opt));
})
} else {
ret = [obj];
while (flow[0]) {
if (flow[0].any) {
ret = _find_field(ret, flow[0].any, true); //deep find_field
} else
if (flow[0].next) {
ret = _find_field(ret, flow[0].next);
} else {
ret = []
}
if (flow[0].transformation) {
flow[0].transformation.forEach(_transformation => {
let filtered_ret = ret;
if (_transformation.filter) {
filtered_ret = []
ret.forEach(_itm => {
let o = _obj_filter(_itm, _transformation.filter, opt);
if (o) filtered_ret = filtered_ret.concat(o);
})
}
if (_transformation.pseudo) {
filtered_ret = _obj_pseudo(ret, _transformation.pseudo, opt);
}
ret = filtered_ret;
})
}
flow.splice(0, 1);
}
}
ret = dedup(ret); //dedup as "a b c" at {a:{b:{b:{c:{z:1}}}}} can return [{z:1}, {z:1}]
return ret;
}
const _find_field = (obj, fieldName, deep) => {
let ret = [];
if (obj) {
if (obj instanceof Array) {
obj.forEach(_itm => {
ret = ret.concat(_find_field(_itm, fieldName, deep));
})
} else {
if (fieldName==='*') {
if (typeof obj == 'object') {
if (deep) ret.push(obj); // ".*" does not include current level (while " *" does)
for (let i in obj) {
ret.push(obj[i]);
if (deep) ret = ret.concat(_find_field(obj[i], fieldName, true));
}
} else {
ret.push(obj);
}
} else {
if (obj[fieldName]) {
if (obj[fieldName] instanceof Array) {
ret = ret.concat(flatten(obj[fieldName]));
} else {
ret.push(flatten(obj[fieldName]));
}
}
if (deep && typeof obj == 'object') {
for (let i in obj)
ret = ret.concat(_find_field(obj[i], fieldName, true));
}
}
}
}
return ret;
}
const _obj_pseudo = (obj, pseudoName, opt) => {
let localPseudo = Object.assign({}, pseudo, (opt || {}).pseudo);
if (!localPseudo[pseudoName]) {
//You can comment this line and it will use (x)=>{return x} instead of absent pseudo.
throw new Error("Pseudo '"+pseudoName+"' not found.");
}
const func = localPseudo[pseudoName] || function(){return obj};
return func(obj);
}
//remove items of multiple values (i.e. from arrays) that does not satisfies filter (at any level of nested of object)
const _obj_filter = (obj, filter, opt) => {
const filterParsed = parse_filter(filter, opt);
if (!filterParsed.left) {
return obj;
}
if (!_obj_satisfies_filter(obj, filterParsed, opt)) {
return;
} else {
return deep_filter(obj, (_itm, parent, parent_key) => {
if (_itm instanceof Array) {
let filtered = [];
if (!parent) {
for (let i=0; i<_itm.length; i++) {
if (_obj_satisfies_filter(_itm[i], filterParsed, opt)) filtered.push(_itm[i]);
}
} else {
//by the way, parent[parent_key] == _itm, _itm is array
let saved = parent[parent_key];
const parent_backup = (parent instanceof Array ? [] : {});
for (let j in parent) {
parent_backup[j] = parent[j];
parent[j] = undefined;
}
for (let i=0; i<saved.length; i++) {
parent[parent_key] = [saved[i]];
if (_obj_satisfies_filter(obj, filterParsed, opt)) {
filtered.push(saved[i]);
}
}
for (let j in parent_backup) {
parent[j] = parent_backup[j];
}
parent[parent_key] = filtered;
}
_itm = filtered;
}
return _itm
});
}
}
//is 'obj' satisfies 'filter' condition
//(filter can be like this "a.b.c=d", that means obj.a.b.c = d)
//(if obj.a.b.c returns array (look at _get) then it returns true if it contains filter value)
const _obj_satisfies_filter = (obj, filterParsed, opt) => {
let localOperator = Object.assign({}, operator, (opt || {}).operator);
const complexField = filterParsed.left;
const value = filterParsed.right;
const equal = localOperator[filterParsed.delimiter] || function(){};
let complexFieldValue = _get(obj, parse(complexField), opt);
return equal(complexFieldValue, value);
}
module.exports = { get };