Last active
February 1, 2016 16:22
-
-
Save ironboy/461806890b583c7cdb86 to your computer and use it in GitHub Desktop.
Search JS data structures with ease
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
var propSearch = (function(){ | |
/* | |
ironboy, Jan 2016 | |
Search an endlessly deep/complex JS data structure with ease | |
Call this function with a search object as your input parameter. | |
searchObj: | |
data: the data structure to search within | |
val: the value to search for | |
string, number, boolean, object, array or a reg exp | |
getSearchIndex: instead of a search get the whole search index (defaults to false) | |
if set to data only specify data | |
prop: property to search within (defaults to any) | |
string | |
_class: class to search within (defaults to any) | |
can by a constructor (function) or name of constructor (string) | |
strict: only match the exact type of val | |
boolean, defaults to false | |
regExpMethod: can be test, match, exec or search | |
string, defaults to test | |
- https://toddmotto.com/understanding-regular-expression-matching-with-test-match-exec-search-and-split/ | |
duplicates: set to false to avoid finding the same object twice | |
(boolean, defaults to true) | |
indexRebuild: set to true if you have made ANY changes to the data | |
(boolean, defaults to false) | |
In return you get a result array containing objects. | |
Each object has the following properties: | |
key: The found key/property name (string) | |
val: The found value (mixed) | |
obj: The object in which the key/val pair was found | |
jsonPath: the json path to this property within the data structure | |
If you search using the regExp methods match, exec or search | |
a fourth property will be present: | |
regExpResult: Detailed result according to the reg exp method | |
- https://toddmotto.com/understanding-regular-expression-matching-with-test-match-exec-search-and-split/ | |
*/ | |
var propSearchMem = {}, memCo = 0; | |
function search(searchObj){ | |
var result, | |
hit, | |
mco, | |
s = searchObj, | |
m = propSearchMem, | |
d = s.data || {}, | |
v = s.val || '', | |
p = s.prop, | |
c = s._class, | |
r = s.regExpMethod || 'test', | |
noDups = s.duplicates === false, | |
noDupsMem = [], | |
strict = s.strict, | |
abstract = v && typeof v == "object"; | |
// sanitze reg exp method | |
r = ['test','match','exec','search'].indexOf(r) >= 0 ? r : 'test'; | |
// create search index if not created | |
if(!d.__inPropSearchMem || s.indexRebuild){ | |
(!s.indexRebuild || !d.__inPropSearchMem) && memCo++; | |
mco = (!s.indexRebuild || !d.__inPropSearchMem) ? memCo : d.__inPropSearchMem; | |
d.__inPropSearchMem = mco; | |
m[mco] = createSearchIndex(d); | |
} | |
// find the search index of the dataStructure | |
m = m[d.__inPropSearchMem]; | |
if(s.getSearchIndex){return m;} | |
// search (superfast, since hash lookup, non abstract, non reg exp) | |
if(!abstract && v.constructor !== RegExp){ | |
result = m[v + ''] || []; | |
} | |
// search by regular expression match (regexp, non abstract) | |
if(v.constructor === RegExp){ | |
result = []; | |
for(var i in m){ | |
if(!m.hasOwnProperty(i) || i.indexOf('__object__:')===0){continue;} | |
hit = (r == "test" || r == "exec") && v[r](i); | |
hit = hit || (r == "match" && i[r](v)); | |
hit = hit || (r == "search" && i[r](v) >= 0 && i[r](v)); | |
hit = (hit || hit === 0) && {hit:hit}; | |
if(hit && r == "test"){ result = result.concat(m[i]); } | |
if(hit && r != "test"){ | |
// add extra reg exp info to result | |
result = result.concat(m[i].map(function(x){ | |
var y = {}; | |
for(var i in x){ y[i] = x[i]; } | |
y.regExpResult = hit.hit; | |
return y; | |
})); | |
} | |
} | |
} | |
// search for an object (abstract, non reg exp) | |
if(abstract && v.constructor !== RegExp){ | |
result = []; | |
for(var i in m){ | |
if(!m.hasOwnProperty(i) || i.indexOf('__object__:')!==0){continue;} | |
m[i].forEach(function(x){ | |
x.obj === v && result.push(x); | |
}); | |
} | |
} | |
// filter result by propName, className and strict | |
// (className can be a constructor or a constructor name) | |
result = result.filter(function(x){ | |
if(p && x.key != p){return false;} | |
if(c && typeof c == "function" && x.obj.constructor !== c){return false;} | |
if(c && typeof c == "string" && x.obj.constructor.name != c){return false;} | |
if(strict && typeof v !== typeof x.val){return false;} | |
if(noDups){ | |
if(noDupsMem.indexOf(x.obj) >= 0){return false;} | |
noDupsMem.push(x.obj); | |
} | |
return true; | |
}); | |
return result; | |
} | |
function createSearchIndex(val,key,path,objMem,index,obj){ | |
key = key || ''; | |
path = (path || '') + (path && key ? '.' + key : key); | |
objMem = objMem || []; | |
index = index || {}; | |
var abstract = val && typeof val == "object", val2 = val; | |
var nonNumericArrayProp; | |
if(abstract){ | |
if(objMem.indexOf(val) >= 0){return;} | |
objMem.push(val); | |
for(var i in val){ | |
nonNumericArrayProp = val.constructor === Array && i/1 != i/1; | |
if(!val.hasOwnProperty(i) || nonNumericArrayProp){continue;} | |
createSearchIndex(val[i],i,path,objMem,index,val); | |
} | |
val2 = "__object__:" + val.constructor.name; | |
} | |
index[val2] = index[val2] || []; | |
index[val2].push({ | |
key: key, | |
val: val, | |
jonPath: path, | |
obj: abstract ? val : obj | |
}); | |
return index; | |
} | |
return search; | |
})(); | |
// ------------------------------------ | |
// Test data | |
function Person(x){ | |
for(var i in x){this[i]=x[i];} | |
} | |
function Pet(x){ | |
for(var i in x){this[i]=x[i];} | |
} | |
var persons = [ | |
new Person({ | |
id:1, | |
name: "Olle", | |
pets: [ | |
new Pet({name:"Doggie",id:1,yo:'clumsy'}), | |
new Pet({name:"Caty",id:2}) | |
] | |
}), | |
new Person({ | |
id:2, | |
name: "Anna Olson", | |
pets: [ | |
new Pet({name:"Roger Rabbit",id:3,favNumber:"3"}), | |
new Pet({name:"Spiderman",petName:"Olle",id:4}) | |
] | |
}) | |
]; | |
// Test searches | |
console.log(propSearch({data:persons,val:"Olle"})); | |
console.log(propSearch({data:persons,val:/Anna/})); | |
console.log(propSearch({data:persons,val:1,prop:'id'})); | |
console.log(propSearch({data:persons,val:persons})); | |
console.log(propSearch({data:persons,val:/Ol/})); | |
console.log('reg exp test',propSearch({data:persons,val:/Ol/})); | |
console.log('reg exp search',propSearch({data:persons,val:/Ol/,regExpMethod:'search'})); | |
console.log(propSearch({data:persons,val:3,strict:true})); | |
console.log(propSearch({data:persons,val:"3",strict:true})); | |
persons[0].yo = "clumsy"; | |
console.log("no index-rebuild",propSearch({data:persons,val:'clumsy'})); | |
console.log("index-rebuild",propSearch({data:persons,val:'clumsy',indexRebuild:true})); | |
console.log('dups',propSearch({data:persons,val:3})); | |
console.log('no-dups',propSearch({data:persons,val:3,duplicates:false})); | |
// circular? | |
persons[1].friend = persons[0]; | |
persons[0].friend = persons[1]; | |
console.log("works after circular",propSearch({indexRebuild:true,data:persons,val:"Olle"})); | |
// get index; | |
console.log("THE INDEX",propSearch({data:persons,getSearchIndex:true})); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment