Skip to content

Instantly share code, notes, and snippets.

@pingec
Created September 12, 2019 17:21
Show Gist options
  • Save pingec/76e9da73fb4b12dd0fb1de77f84f71cf to your computer and use it in GitHub Desktop.
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
// 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