Eureka! I figured it out! The below answer is in the form of a library I'm writing but it should be relatively easy to follow.
As it turns out, the best course of action (as far as I can tell) is to first using a separate function, build an object from the target object containing all the property paths:
/**
* Returns an object containing all of the property paths of an object. Each
* property path is referenced by the property name.
* @param {object} The object to target
* @return {object} Object containing paths ELSE undefined
*/
paths: function( obj, path, lastKey, nextKey ) {
var o, key,
path = path ? path : {},
lastKey = lastKey ? lastKey : "",
nextKey = nextKey ? nextKey : "";
for ( o in obj ) {
// Push path onto stack
path[o] = (nextKey + "." + lastKey + "." + o).replace(/^[.]+/g, "");
// Pass updated "nextKey" along with next recurse
key = nextKey + "." + lastKey;
// Call again on all nested objects
if ( (lib).isPlainObject(obj[o]) ) {
(lib).paths(obj[o], path, o, key);
}
}
return (lib).len(path) ? path : undefined;
},
Then we use the resolve method as a "wrapper" to the paths method, returning the targeted property key's namespace.
resolve: function( obj, key ) {
return (lib).paths(obj)[key];
},
Using the object I posted originally above:
var res = o.resolve(obj, "A_2_b_1");
// Returns "A.A_2.A_2_b.A_2_b_1"
Just for reference, the paths
method returns an object that looks something like this:
// {
// A: [...]
// A_1: [...]
// A_2: [...]
// A_2_a: [...]
// A_2_b: [...]
// A_2_b_1: [
// 0: "A_2_b_1"
// 1: "A.A_2.A_2_b.A_2_b_1"
// ]
// A_2_b_2: [...]
// A_2_c: [...]
// A_3: [...]
// B: [...]
// ...
// }
Where each property maps to its path in the object.