24

I have a javascript object, and I want to recursively search it to find any properties that contain a specific value.

The javascript I'm working with has been minified, and is not so easy to trace through.

Background

I'm using the Bing Maps AJAX SDK. It has the ability to add additional tile layers. Each tilelayer has a tilesource object, which specifies the URI format for the tile URL.

I've ran into a problem where the tilesource URI is created once, and cached. Thus I can't dynamically change the parameters of the URL (for example, to change the colors of the tile overlay based on the time of day) for each request.

Note that this behavior is different that Google's Map API, and the Bing Maps api for WP7, which both allow you to dynamically create the URL for each tile request.

The cached URI is looked up, and two specific parameters are replaced, then the URI is used to fetch the tile.

Since this is javascript, I'd like to find the cached URI, and replace it with a function, that instead dynamically builds the URI, and returns it.

I don't need to do this each runtime, just want and idea of where the property is being cached, so I can write code to hax0r it.

Original Question

If I set the URI to some value like "floobieblaster", when I set a breakpoint, can I search the javascript object recursively for "floobieblaster" and get the property that is storing that value?

Edit to add

The object I'm searching appears to have a circular reference, thus any recursive code will likely cause a stackoverflow.

Are there any editor/debugger tricks I could make use of?

2
  • I think I can potentially dump the object out to json and search it that way. Perhaps there is a better way?-- Nope. Looks like the object has a circular structure.
    – Alan
    Commented Feb 23, 2012 at 22:57
  • Crockford's library (see cycle.js) github.com/douglascrockford/JSON-js supports encoding and decoding of circular objects into json (+ jsonpath). You can serialise and search like you suggested, or just change the code slightly to achieve your goal directly.
    – davin
    Commented Feb 23, 2012 at 23:50

7 Answers 7

30

Something simple like this should work:

var testObj = {
    test: 'testValue',
    test1: 'testValue1',
    test2: {
        test2a: 'testValue',
        test2b: 'testValue1'
    }
}

function searchObj (obj, query) {

    for (var key in obj) {
        var value = obj[key];

        if (typeof value === 'object') {
            searchObj(value, query);
        }

        if (value === query) {
            console.log('property=' + key + ' value=' + value);
        }

    }

}

If you execute searchObj(testObj, 'testValue'); it will log the following to the console:

property=test value=testValue
property=test2a value=testValue

Obviously, you can replace the console.log with whatever you want, or add a callback parameter to the searchObj function to make it more reusable.

EDIT: Added the query parameter which allows you to specify the value you want to search for when you call the function.

1
  • 7
    +1 but to prevent searching objects on the [[Prototype]] chain, a hasOwnProperty test should be included. Consider testing for typeof obj[key] == 'function' too.
    – RobG
    Commented Feb 24, 2012 at 0:26
4

This function will Search in Object. It’ll match Search Query with Object’s every property.This is useful when you need to search in multidimensional object After spending hours I got this code from Google’s AngularJS Project.

/* Seach in Object */

var comparator = function(obj, text) {
if (obj && text && typeof obj === 'object' && typeof text === 'object') {
    for (var objKey in obj) {
        if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) &&
                comparator(obj[objKey], text[objKey])) {
            return true;
        }
    }
    return false;
}
text = ('' + text).toLowerCase();
return ('' + obj).toLowerCase().indexOf(text) > -1;
};

var search = function(obj, text) {
if (typeof text == 'string' && text.charAt(0) === '!') {
    return !search(obj, text.substr(1));
}
switch (typeof obj) {
    case "boolean":
    case "number":
    case "string":
        return comparator(obj, text);
    case "object":
        switch (typeof text) {
            case "object":
                return comparator(obj, text);
            default:
                for (var objKey in obj) {
                    if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
                        return true;
                    }
                }
                break;
        }
        return false;
    case "array":
        for (var i = 0; i < obj.length; i++) {
            if (search(obj[i], text)) {
                return true;
            }
        }
        return false;
    default:
        return false;
}
};
2
  • 2
    This apparently handles recursive structures, which the accepted answer does not.
    – Automatico
    Commented Nov 9, 2017 at 21:27
  • this does not handle recursive structures, i have tried it (infinite recursion, it needs a dict to keep track of what it is currently searching). also case array will never be reached because typeof [] is object. it also fails if an exception occurs when accessing an object's key. i have fixed these issues in my answer Commented May 12, 2023 at 19:53
4

Here are some modern solutions to this old question. You can extend it to suit your own needs. Assume the following data structure:

table = {
  row1: {
    col1: 'A',
    col2: 'B',
    col3: 'C'
  },
  row2: {
    col1: 'D',
    col2: 'A',
    col3: 'F'
  },
  row3: {
    col1: 'E',
    col2: 'G',
    col3: 'C'
  }
};

To get an array of keys to objects where property col3 equals to "C":

Object.keys(table).filter(function(row) {
  return table[row].col3==='C';
});

This would return ['row1', 'row3'].

To get a new object of rows where property col3 equals to "C":

Object.keys(table).reduce(function(accumulator, currentValue) {
  if (table[currentValue].col3==='C') accumulator[currentValue] = table[currentValue];
  return accumulator;
}, {});

This would return

{
  row1: {
    col1: 'A',
    col2: 'B',
    col3: 'C'
  },
  row3: {
    col1: 'E',
    col2: 'G',
    col3: 'C'
  }
}

Note that the answers above are derived from a similar question.

2

Here's my solution, it matches the given string/value with a regex test and returns the matched array. It's not recursive, however you have removed this from your question.

This is from my answer at the following thread: Search a JavaScript object

Same principles, as others have suggested - searching an object for the given value, for anyone in search of this solution.

The function:

Array.prototype.findValue = function(name, value){
   var array = $.map(this, function(v,i){
        var haystack = v[name];
        var needle = new RegExp(value);
        // check for string in haystack
        // return the matched item if true, or null otherwise
      return needle.test(haystack) ? v : null;
   });
  return this;
}

Your object:

myObject = {
        name : "soccer",
        elems : [
            {name : "FC Barcelona"},
            {name : "Liverpool FC"}
        ]
    },
    {
        name : "basketball",
        elems : [
            {name : "Dallas Mavericks"}
        ]
    }

For usage:

(This will search your myObject.elems array for a 'name' matching 'FC')

var matched = myObject.elems.findValue('name', 'FC');
console.log(matched);

The result - check your console:

[Object, Object, keepMatching: function, findValue: function]
0: Object
name: "FC Barcelona"
__proto__: Object
1: Object
name: "Liverpool FC"
__proto__: Object
length: 2
__proto__: Array[0]

If you wanted an exact match you'd simply change the regex in the ternary statement to a basic value match. i.e.

v[name] === value ? v : null 
1

Here is a more convenient ready-to-go static method based on Bryan's approach:

/**
* Find properties matching the value down the object tree-structure.
* Ignores prototype structure and escapes endless cyclic nesting of
* objects in one another.
*
* @param {Object} object Object possibly containing the value.
* @param {String} value Value to search for.
* @returns {Array<String>} Property paths where the value is found. 
*/
getPropertyByValue: function (object, value) {
  var valuePaths;
  var visitedObjects = [];

  function collectValuePaths(object, value, path, matchings) {

    for (var property in object) {

      if (
        visitedObjects.indexOf(object) < 0 &&
        typeof object[property] === 'object') {

        // Down one level:

        visitedObjects.push(
          object);

        path =
          path +
          property + ".";

        collectValuePaths(
          object[property],
          value,
          path,
          matchings);
      }

      if (object[property] === value) {

        // Matching found:

        matchings.push(
          path +
          property);
      }

      path = "";
    }

    return matchings;
  }

  valuePaths =
    collectValuePaths(
      object,
      value,
      "",
      []);

   return valuePaths;
}

For an object

var testObj = {
  test: 'testValue',
  test1: 'testValue1',
  test2: {
      test2a: 'testValue',
      test2b: 'testValue1'
  }
}

will result in

["test", "test2.test2a"]
1
  • Hi Zon, how would I call this function? I am getting an error calling getPropertyByValue Commented Mar 29, 2023 at 14:13
0

I edited Bryan Downing answer to print an hierarchy for deep objects:

   function searchObj (obj, query, prefix /*not to be set*/) {
    prefix = prefix || "---";
    var printKey;

    for (var key in obj) {
        var value = obj[key];

        if (typeof value === 'object') {
            if (searchObj(value, query, prefix + "|---")) {
                console.log(prefix + ' ' + key);
                printKey = true;
            }
        }

        if (value === query) {
            console.log(prefix + ' ' + key + ' = ' + value);

            return true;
        }
    }

    return printKey;
}

Then, run searchObj(testObj, 'testValue');

1
  • 1
    I get a maximum stack exceeded error in Chrome dev tools when I use this as a snippet to search the window object for a property Commented Aug 6, 2019 at 19:24
0

Here is a way to do it, strongly recommended for the dev console only:

const Searcher = {
    search: function(obj, text) {
        const backtracks = this.searchHelper(obj, text);
        const ret = []
        for (const b of backtracks) {
            let cur = b
            let path = []
            while (cur != null) {
                path.push(cur.key)
                cur = cur.parent
            }
            ret.push({path: path.reverse(), backtrack: b})
        }
        return ret.sort((a,b) => a.path.length - b.path.length)
    },
    comparator: function (primitive, text) {
        return ('' + primitive).indexOf(text) > -1;
    },
    searchHelper: function(start, text) {
        const marker = 'a' + ('' + Math.random()).slice(2)
        const frontier = []
        const seen = []
        const ret = []
        frontier.push({obj: start, key: 'start', parent: null})
        while (frontier.length > 0) {
            let f = frontier.pop()
            let obj = f.obj
            switch (typeof obj) {
                case "object": {
                    if (obj === null) continue
                    seen.push(obj)
                    try {
                        if (obj[marker] === null) continue
                        obj[marker] = null
                    } catch (e) {
                        console.log("Exception: " + e + ", continuing...");
                        continue
                    }
                    for (const objKey in obj) {
                        try {
                            frontier.push({obj: obj[objKey], key: objKey, parent: f})
                            if (this.comparator(objKey, text)) ret.push(frontier[frontier.length - 1])
                        } catch (e) {
                            console.log("Exception: " + e + ", continuing...");
                            continue
                        }
                    }
                }
                break
                default:
                    if (this.comparator(obj, text)) ret.push(f)
            }
        }
        for (v of seen) {
            try {
                delete v[marker]
            } catch (e) {
                console.log("Exception while cleaning: " + e + ", continuing...");
            }
        }
        return ret
    }
}

The changes are:

  • Supports recursive objects
  • Also searches for key matches
  • Does not fail on exceptions
  • Returns path as well
  • Runs much faster by modifying the objects themselves
  • No recursion depth limit (non-recursive)

Please do not use this in any kind of production code or capacity. It is purely for pasting in the dev tools console to experiment with pages. The reason I do not recommend it otherwise is:

  • It modifies objects as it goes, which can cause issues
  • If an object somehow changes its own properties it could tamper with the marker causing an infinite loop
  • 99% CPU load while it is running

Try it with:

Searcher.search(window, 'jsHeapSizeLimit')

in a new tab (tested on Chrome)

Not the answer you're looking for? Browse other questions tagged or ask your own question.