Last active
March 10, 2016 14:52
-
-
Save Xananax/a700a263bf83f0207540 to your computer and use it in GitHub Desktop.
Looks up deep values in a javascript object or array
This file contains hidden or 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
/** | |
// Super simple version of the below; if you just want to look up deep values, that's all you need: | |
function lookup_simple(obj,path,delimiter='.'){ | |
if(obj==null || !path || !path.length){return obj;} | |
path = Array.isArray(path) ? path : path.split(delimiter); | |
const rest = path.slice(1); | |
const current = obj[path[0]]; | |
return (rest.length ? lookup_simple(current,rest) : current) | |
} | |
// ...And that's all that's needed for the simple lookup. For a more advanced version, read on | |
**/ | |
/** | |
* Get deep values inside objects or arrays. Accepts wildcards ('*') | |
* and slice ('[x-y]'). | |
* This function only slices the path if it is not an array, then passes | |
* control to the actual 'lookup' function below | |
* @param {Object|Array} obj the object to look into | |
* @param {Array|String} path the path | |
* @param {String} delimiter delimiter to cut the path with. '.' by default | |
* @return {any} returned value(s) | |
*/ | |
function _lookup(obj,path,delimiter='.'){ | |
const parts = isArray(path) ? path : path.split(delimiter); | |
return lookup(obj,parts); | |
} | |
/** | |
* Actual lookup function. Recursive function that will | |
* use the next key in an array to find a deep value. | |
* If the key matches certain parameters, it will pass control | |
* to other functions. | |
* @param {Object|Array} obj the object to look into | |
* @param {Array} path the path | |
* @return {any} | |
*/ | |
function lookup(obj,path){ | |
if(obj==null || !path || !path.length){return obj;} | |
const key = path[0]; | |
const rest = path.slice(1); | |
// if you simply want a deep value retrieval with no special handling | |
// of specific characters, just remove everything below | |
// until 'const current = obj[key]' | |
// checking if the key matches conditions... | |
const nextFn = map(key,obj,path); | |
if(nextFn){ //if it does, pass control to that function | |
return nextFn(key,obj,rest); | |
} | |
// if it doesn't, simply get the next key and stroll along. | |
const current = obj[key]; | |
return (rest.length ? lookup(current,rest) : current) | |
} | |
/** | |
* Returns a function to apply to an object, if the key | |
* matches specific conditions. If no filter function is found, the | |
* function returns undefined. | |
* Currently, the function checks if the key matches '*', in which case | |
* it will return the 'gather' function, or if the key matches '[x-y]', | |
* in which case it will return the 'slice' function. If you want more handling | |
* of special characters in your path, here is the place to add them. | |
* @param {String} key the current key | |
* @param {any} obj An object or an array | |
* @param {Array} path a path | |
* @return {Function|undefined} Either a suitable filtering function, or undefined | |
*/ | |
function map(key,obj,path){ | |
if(key==='*'){ | |
return gather; | |
} | |
if(slice_regex.test(key)){ | |
return slice; | |
} | |
} | |
/** | |
* Retrieves every key in an array that matches the given path. | |
* If no value is retrieved, the function returns undefined. | |
* @param {Array} obj the array to look into | |
* @param {Array} path a path | |
* @return {Array|undefined} A new array made of the gathered sub-object, or undefined | |
*/ | |
function gatherArray(obj,path){ | |
const result = [] | |
, length = obj.length | |
; | |
let i = 0 | |
, found = false | |
; | |
while(i<length){ | |
let ret = lookup(obj[i++],path); | |
if(!isUndefined(ret)){ | |
found = true; | |
result.push(ret); | |
} | |
} | |
if(found){return result;} | |
return; | |
} | |
/** | |
* Retrieves every key in an object that matches the given path. | |
* If no value is retrieved, the function returns undefined. | |
* @param {Object} obj the object to look into | |
* @param {Array} path a path | |
* @return {Object|undefined} an object with keys matching the path, or undefined | |
*/ | |
function gatherObject(obj,path){ | |
const result = {} | |
, keys = Object.keys(obj) | |
, length = keys.length | |
; | |
let i = 0 | |
, found = false; | |
; | |
while(i<length){ | |
let k = keys[i++]; | |
let ret = lookup(obj[k],path); | |
if(!isUndefined(ret)){ | |
found = true; | |
result[k] = ret; | |
} | |
} | |
if(found){return result;} | |
return; | |
} | |
/** | |
* Retrieves every key matching a given path in an object or array. | |
* If the given object is neither an object or an array, the function returns undefined. | |
* @param {String} key has no use in this function | |
* @param {Array|Object} obj the object to look into | |
* @param {Array} path the path | |
* @return {Array|Object|undefined} A new object or array, or undefined | |
*/ | |
function gather(key,obj,path){ | |
if(isArray(obj)){ | |
return gatherArray(obj,path); | |
} | |
if(isObject(obj)){ | |
return gatherObject(obj,path); | |
} | |
return; | |
} | |
const slice_regex = /\[(\d+)-(\d+)\]/; | |
/** | |
* Slices an array with a key '[x-y]' where x is the start, and y the end | |
* @param {String} key A key matching the pattern '[x-y]' | |
* @param {Array} obj An Array | |
* @param {Array} path A path to further deep-get values | |
* @return {Array|undefined} An array of results | |
*/ | |
function slice(key,obj,path){ | |
if(isArray(obj)){ | |
const limits = (key.match(slice_regex) || []); | |
const [,start,end] = limits; | |
const sliced = obj.slice(start,end); | |
if(!path.length){ | |
return sliced; | |
} | |
return gatherArray(sliced,path); | |
} | |
} | |
/** Utilities functions **/ | |
function isUndefined(obj){return typeof obj === 'undefined';} | |
function isArray(obj){return Array.isArray(obj);} | |
function isObject(obj){return (obj && typeof obj === 'object' && obj.constructor == Object);} | |
/** | |
* Quick and dirty testing of the lookup function | |
* / | |
function test(){ | |
var test1 = { | |
a:'A' | |
, b:'B' | |
, c:{ | |
cA:'CA' | |
, cB:'CB' | |
} | |
, d:[ | |
{ | |
d0a:'D0A' | |
, d0b:'D0B' | |
} | |
, { | |
'd1A':'D1A' | |
, 'd0a':'D1B' | |
} | |
] | |
, e:[ | |
'e0','e1','e2','e3','e4','e5' | |
] | |
, f:[ | |
{ | |
a:{ | |
'aa':'FAA' | |
, 'ab':'FAB' | |
} | |
} | |
, { | |
a:{ | |
aa:'FBA' | |
} | |
} | |
, { | |
c:{ | |
aa:'FCA' | |
} | |
} | |
, { | |
a:{ | |
aa:'FDA' | |
, ab:'FDB' | |
} | |
} | |
] | |
} | |
console.log(_lookup(test1,'d.*.d0a')) | |
console.log(_lookup(test1,'e.[1-3]')) | |
console.log(_lookup(test1,'f.*.*.aa')) | |
console.log(_lookup(test1,'f.*.*.ab')) | |
} | |
test(); | |
/**/ | |
exports = module.exports = _lookup; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment