Skip to content

Instantly share code, notes, and snippets.

@Ratismal
Last active September 8, 2021 17:58
Show Gist options
  • Save Ratismal/8f2659bf7b0cf50a8fc9b8d8343f2b84 to your computer and use it in GitHub Desktop.
Save Ratismal/8f2659bf7b0cf50a8fc9b8d8343f2b84 to your computer and use it in GitHub Desktop.
A function that will scan through an object to find a specific term. Made for investigating datalayers.

Object Scanner

A function that will scan through an object to find a specific term or value. It searches recursively, and ignores duplicate objects along a path.

This function was made for performing datalayer explorations. It's not a perfect solution, as it cannot call functions or find locally scoped values.

Usage

Copy 1_object_scanner.min.js and paste it onto desired page. Run the function searchForTerm(term, options). See object_scanner.js for usage information.

Ex.

searchForTerm('term');
searchForTerm(function(value) {
  return value === 'term';
});
searchForTerm('term', {
  verbose: true,
});

Bookmarklet

You can also save the snippet as a bookmarklet by saving the contents of 2_object_scanner.bookmarklet.min.js as a bookmark.

function searchForTerm(o,e={}){const t={results:[],i:0,options:e={maxIterations:65536,maxDepth:8,verbose:!1,root:window,...e}};let i;return"function"==typeof o?i=o:(o=o.toString().toLowerCase(),i=function(e){return e.toString().toLowerCase().includes(o)}),function o(n,r){if(t.i++,r.length>=e.maxDepth)e.verbose&&console.log("Exceeded maximum depth (%i/%i), breaking...",r.length,e.maxDepth);else{e.verbose&&console.log("[%i] Scanning: %s",t.i,r.join("."));for(const s of Object.keys(n)){const c=n[s],a=[...r,s];if(n===c||c===e.root)continue;let l,u=e.root,f=!0;for(const o of r)if((u=u[o])===c){e.verbose&&console.log("Skipping %s, duplicate object",a.join(".")),f=!1;break}if(f){if(t.i>=t.options.maxIterations)return console.log("Exceeded maximum iterations (%i/%i), terminating...",t.i,e.maxIterations),!1;if(c&&("object"==typeof c?l=o(c,a):c&&i(c)&&t.results.push({obj:n,level:a.join("."),val:c}),!1===l))return!1}}}}(e.root,[]),t.results}
javascript:!function(){function e(e,o={}){const t={results:[],i:0,options:o={maxIterations:65536,maxDepth:8,verbose:!1,root:window,...o}};let r;return"function"==typeof e?r=e:(e=e.toString().toLowerCase(),r=function(o){return o.toString().toLowerCase().includes(e)}),function e(n,i){if(t.i++,i.length>=o.maxDepth)o.verbose&&console.log("Exceeded maximum depth (%i/%i), breaking...",i.length,o.maxDepth);else{o.verbose&&console.log("[%i] Scanning: %s",t.i,i.join("."));for(const s of Object.keys(n)){const a=n[s],c=[...i,s];if(n===a||a===o.root)continue;let l,u=o.root,m=!0;for(const e of i)if((u=u[e])===a){o.verbose&&console.log("Skipping %s, duplicate object",c.join(".")),m=!1;break}if(m){if(t.i>=t.options.maxIterations)return console.log("Exceeded maximum iterations (%i/%i), terminating...",t.i,o.maxIterations),!1;if(a&&("object"==typeof a?l=e(a,c):a&&r(a)&&t.results.push({obj:n,level:c.join("."),val:a}),!1===l))return!1}}}}(o.root,[]),t.results}console.log(["===[ Object Scanner ]===","See https://gist.github.com/Ratismal/8f2659bf7b0cf50a8fc9b8d8343f2b84 for usage instructions.","","Examples:","searchForTerm('term');","searchForTerm(function(value) {"," return value === 'term';","});","searchForTerm('term', {"," verbose: true,","});"].join("%5Cn"));try{window.searchForTerm=e}catch(o){try{global.searchForTerm=e}catch(e){console.error("Could not append searchForTerm to a global namespace (tried `window` and `global`)")}}}();
/**
* Scans through an object to find a specified term somewhere within the structure
* @param {string|function} term - The term to search for.
* If it's a function, will be used to determine matches manually - takes a string as input, and should return a boolean.
* Otherwise, will coerce to a lowercased string for checks.
* @param {object} [options] The options for scanning
* @param {number} [options.maxIterations=65536] - The maximum number of objects to scan before terminating (to prevent endless loops)
* @param {number} [options.maxDepth=8] - The maximum depth of recursion to go to for any given path
* @param {boolean} [options.verbose=false] - Whether or not to provide verbose debug logging
* @param {object} [options.root=window] - The root object to scan. Defaults to window.
* @returns {object[]} Search results, containing the value, object, and level for each find
*/
function searchForTerm(term, options = {}) {
options = {
maxIterations: 65536,
maxDepth: 8,
verbose: false,
root: window,
...options
};
const state = {
results: [],
i: 0,
options
};
let func;
if (typeof term === 'function') {
func = term;
} else {
term = term.toString().toLowerCase();
func = function(k) {
return k.toString().toLowerCase().includes(term);
}
}
function scan(obj, level) {
state.i++;
if (level.length >= options.maxDepth) {
if (options.verbose) {
console.log('Exceeded maximum depth (%i/%i), breaking...', level.length, options.maxDepth);
}
return;
}
if (options.verbose) {
console.log('[%i] Scanning: %s', state.i, level.join('.'))
}
for (const key of Object.keys(obj)) {
const val = obj[key];
const l = [...level, key];
if (obj === val || val === options.root) {
continue;
}
let o = options.root;
let cont = true;
for (const lev of level) {
o = o[lev];
if (o === val) {
if (options.verbose) {
console.log('Skipping %s, duplicate object', l.join('.'));
}
cont = false;
break;
}
}
if (!cont) {
continue;
}
if (state.i >= state.options.maxIterations) {
console.log('Exceeded maximum iterations (%i/%i), terminating...', state.i, options.maxIterations);
return false;
}
if (!val) continue;
let res;
if (typeof val === 'object') {
res = scan(val, l);
} else if (val && func(val)) {
state.results.push({
obj, level: l.join('.'), val
});
}
if (res === false) {
return false;
}
}
}
scan(options.root, []);
return state.results;
}
(function() {
/**
* Scans through an object to find a specified term somewhere within the structure
* @param {string|function} term - The term to search for.
* If it's a function, will be used to determine matches manually - takes a string as input, and should return a boolean.
* Otherwise, will coerce to a lowercased string for checks.
* @param {object} [options] The options for scanning
* @param {number} [options.maxIterations=65536] - The maximum number of objects to scan before terminating (to prevent endless loops)
* @param {number} [options.maxDepth=8] - The maximum depth of recursion to go to for any given path
* @param {boolean} [options.verbose=false] - Whether or not to provide verbose debug logging
* @param {object} [options.root=window] - The root object to scan. Defaults to window.
* @returns {object[]} Search results, containing the value, object, and level for each find
*/
function searchForTerm(term, options = {}) {
options = {
maxIterations: 65536,
maxDepth: 8,
verbose: false,
root: window,
...options
};
const state = {
results: [],
i: 0,
options
};
let func;
if (typeof term === 'function') {
func = term;
} else {
term = term.toString().toLowerCase();
func = function(k) {
return k.toString().toLowerCase().includes(term);
}
}
function scan(obj, level) {
state.i++;
if (level.length >= options.maxDepth) {
if (options.verbose) {
console.log('Exceeded maximum depth (%i/%i), breaking...', level.length, options.maxDepth);
}
return;
}
if (options.verbose) {
console.log('[%i] Scanning: %s', state.i, level.join('.'))
}
for (const key of Object.keys(obj)) {
const val = obj[key];
const l = [...level, key];
if (obj === val || val === options.root) {
continue;
}
let o = options.root;
let cont = true;
for (const lev of level) {
o = o[lev];
if (o === val) {
if (options.verbose) {
console.log('Skipping %s, duplicate object', l.join('.'));
}
cont = false;
break;
}
}
if (!cont) {
continue;
}
if (state.i >= state.options.maxIterations) {
console.log('Exceeded maximum iterations (%i/%i), terminating...', state.i, options.maxIterations);
return false;
}
if (!val) continue;
let res;
if (typeof val === 'object') {
res = scan(val, l);
} else if (val && func(val)) {
state.results.push({
obj, level: l.join('.'), val
});
}
if (res === false) {
return false;
}
}
}
scan(options.root, []);
return state.results;
}
const output = [
'===[ Object Scanner ]===',
'See https://gist.github.com/Ratismal/8f2659bf7b0cf50a8fc9b8d8343f2b84 for usage instructions.',
'',
'Examples:',
'searchForTerm(\'term\');',
'searchForTerm(function(value) {',
' return value === \'term\';',
'});',
'searchForTerm(\'term\', {',
' verbose: true,',
'});',
];
console.log(output.join('\n'));
try {
window.searchForTerm = searchForTerm;
} catch (err) {
try {
global.searchForTerm = searchForTerm;
} catch (err) {
console.error('Could not append searchForTerm to a global namespace (tried `window` and `global`)');
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment