Created
September 12, 2019 17:21
-
-
Save pingec/76e9da73fb4b12dd0fb1de77f84f71cf to your computer and use it in GitHub Desktop.
https://github.com/angus-c/waldojs compiled for use in browser, search javascript objects / object tree
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
// https://github.com/angus-c/waldojs | |
function compare(value1, value2) { | |
if (value1 === value2) { | |
return true; | |
} | |
/* eslint-disable no-self-compare */ | |
// if both values are NaNs return true | |
if ((value1 !== value1) && (value2 !== value2)) { | |
return true; | |
} | |
if ({}.toString.call(value1) != {}.toString.call(value2)) { | |
return false; | |
} | |
if (value1 !== Object(value1)) { | |
// non equal primitives | |
return false; | |
} | |
if (!value1) { | |
return false; | |
} | |
if (Array.isArray(value1)) { | |
return compareArrays(value1, value2); | |
} | |
if ({}.toString.call(value1) == '[object Object]') { | |
return compareObjects(value1, value2); | |
} else { | |
return compareNativeSubtypes(value1, value2); | |
} | |
} | |
function compareNativeSubtypes(value1, value2) { | |
// e.g. Function, RegExp, Date | |
return value1.toString() === value2.toString(); | |
} | |
function compareArrays(value1, value2) { | |
var len = value1.length; | |
if (len != value2.length) { | |
return false; | |
} | |
var alike = true; | |
for (var i = 0; i < len; i++) { | |
if (!compare(value1[i], value2[i])) { | |
alike = false; | |
break; | |
} | |
} | |
return alike; | |
} | |
function compareObjects(value1, value2) { | |
var keys1 = Object.keys(value1).sort(); | |
var keys2 = Object.keys(value2).sort(); | |
var len = keys1.length; | |
if (len != keys2.length) { | |
return false; | |
} | |
for (var i = 0; i < len; i++) { | |
var key1 = keys1[i]; | |
var key2 = keys2[i]; | |
if (!(key1 == key2 && compare(value1[key1], value2[key2]))) { | |
return false; | |
} | |
} | |
return true; | |
} | |
const GLOBAL = (typeof window == 'object') ? window : global; | |
const find = { | |
byName(what, where) { | |
return this.searchMaybe('propName', 'string', what, where); | |
}, | |
byType(what, where) { | |
return this.searchMaybe('type', 'function', what, where); | |
}, | |
byValue(what, where) { | |
return this.searchMaybe('value', null, what, where); | |
}, | |
byValueCoerced(what, where) { | |
return this.searchMaybe('valueCoerced', null, what, where); | |
}, | |
custom(fn, where) { | |
return this.searchMaybe(fn, null, null, where); | |
}, | |
searchMaybe(util, expected, what, where) { | |
// integrity check arguments | |
if (expected && typeof what != expected) { | |
throw new Error(`${what} must be ${expected}`); | |
} | |
// only console.log if we are the global function | |
if (this === GLOBAL.waldo) { | |
GLOBAL.DEBUG = true; | |
} | |
return search(util, what, where); | |
} | |
} | |
function search(util, what, where = GLOBAL) { | |
util = searchBy[util] || util; | |
let data; | |
let alreadySeen; | |
const path = (where == GLOBAL) ? 'GLOBAL' : 'SRC'; | |
let queue = [{ where, path }]; | |
let seen = []; | |
let matches = []; | |
matches.log = function () { | |
this.forEach(m => m.log()); | |
}; | |
// a non-recursive solution to avoid call stack limits | |
// http://www.jslab.dk/articles/non.recursive.preorder.traversal.part4 | |
while ((data = queue.pop())) { | |
let {where, path} = data; | |
for (const prop in where) { | |
// IE may throw errors when accessing/coercing some properties | |
try { | |
if (where.hasOwnProperty(prop)) { | |
// inspect objects | |
if ([where[prop]] == '[object Object]') { | |
// check if already searched (prevents circular references) | |
for ( | |
var i = -1; | |
seen[++i] && !(alreadySeen = compare(seen[i].where, where[prop]) && seen[i]); | |
); | |
// add to stack | |
if (!alreadySeen) { | |
data = { where: where[prop], path: `${path}.${prop}`}; | |
queue.push(data); | |
seen.push(data); | |
} | |
} | |
// if match detected, push it. | |
if (util(what, where, prop)) { | |
const type = alreadySeen ? `<${alreadySeen.path}>` : typeof where[prop]; | |
const match = new Match( | |
{path: `${path}.${prop}`, obj: where, prop, type}); | |
matches.push(match); | |
GLOBAL.DEBUG && match.log(); | |
} | |
} | |
} catch(e) {} | |
} | |
} | |
return matches; | |
} | |
const searchBy = { | |
propName(what, where, prop) { | |
return what == prop; | |
}, | |
type(what, where, prop) { | |
return where[prop] instanceof what; | |
}, | |
value(what, where, prop) { | |
return where[prop] === what; | |
}, | |
valueCoerced(what, where, prop) { | |
return where[prop] == what; | |
} | |
}; | |
class Match { | |
constructor(props) { | |
Object.assign(this, props); | |
this.value = this.obj[this.prop]; | |
} | |
toString() { | |
let {path, type} = this; | |
return `${path} -> (${type}) ${this.logValue()}`; | |
} | |
logValue() { | |
const val = this.value; | |
// if value is an object then just toString it | |
const isPrimitive = x => Object(x) !== x; | |
return isPrimitive(val) || Array.isArray(val) ? | |
val : | |
{}.toString.call(val); | |
} | |
log() { | |
console.log(this.toString()); | |
} | |
} | |
// for console running | |
GLOBAL.waldo = Object.assign({}, find, {debug: true}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment