Skip to content

Instantly share code, notes, and snippets.

@ironboy
Last active February 1, 2016 16:22
Show Gist options
  • Save ironboy/461806890b583c7cdb86 to your computer and use it in GitHub Desktop.
Save ironboy/461806890b583c7cdb86 to your computer and use it in GitHub Desktop.
Search JS data structures with ease
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