8

Given a nested object like this:

var cars = {
    "bentley": {
        "suppliers": [
            {
            "location": "England",
            "name": "Sheffield Mines"}
        ]
        // ...
    }
};

and an array like this ["bentley", "suppliers", "0", "name"], is there an existing function that will pluck the deepest element, i.e. pluck_innards(cars, ['bentley', "suppliers", "0", "name"]) and that returns "Sheffield Mines".

In other words, is there a function (which I will name deep_pluck) where

deep_pluck(cars, ['bentley', 'suppliers', '0', 'name']) 
         === cars['bentley']['suppliers']['0']['name']

It seems to me that this is simple, yet common enough, to have probably been done in one of the Javascript utility libraries such as jQuery or lo-dash/underscore - but I have not seen it.

My thought is something trivial, along the lines of:

function deep_pluck(array, identities) {
    var this_id = identities.shift();
    if (identities.length > 0) {
        return deep_pluck(array[this_id], identities);
    }
    return array[this_id];
}

Which I have posted on jsFiddle.

It would be helpful of course if the function were smart enough to identify when numerical indexes in arrays are needed. I am not sure offhand what other caveats may be a concern.

This is all a fairly long question for something I imagine has already been cleverly solved, but I thought to post this as I would interested in seeing what solutions are out there.

4
  • I'm not sure why this requires a function, or how the function makes things easier in this case..? (Though, as an aside, a ternary shortens your function a little....) Commented Oct 8, 2012 at 16:17
  • @DavidThomas: Not knowing the contents of the identities in advance, how else would you do this? I.e. given the array and an array of items to pluck at incrementally increasing depths, is there an alternative? Commented Oct 8, 2012 at 16:19
  • Ah, I think I misunderstood (I thought you wanted to pass the specific identities to the function, and have the function navigate the object/array). Commented Oct 8, 2012 at 16:21
  • I hope it makes sense now :). Let me know if you can think of any edits that would clarify the question. Commented Oct 8, 2012 at 16:22

4 Answers 4

3

I don't think you'll have problems with Array indexes if you pass them as number 0.

Here's alternative version of your function without recursion:

function deep_pluck(object, identities) {
    var result = object;
    for(var i = 0; i < identities.length; i++) {
        result = result[identities[i]];
    }
    return result;
}

Working example here: http://jsfiddle.net/AmH2w/1/

Sign up to request clarification or add additional context in comments.

1 Comment

This seems better than the unnecessary recursion I was doing. I'll mark this as the answer, because it's a significant improvement over my thoughts. However, an ideal answer I think would be a solution in a pre-existing common and well-maintained library (e.g. lodash/underscore/jquery).
1

dotty.get(obj, pathspec) does it, accepting either an array or a dotted string as the pathspec.

Dotty is open source, and also has an exists method, and a putter.

The methodology is recursion and very similar to your idea, except that dotty includes a test for null/undefined objects so that it doesn't throw exceptions for trying to access an element of something that doesn't exist.

The dotty.get() source from the docs is posted below:

var get = module.exports.get = function get(object, path) {
  if (typeof path === "string") {
    path = path.split(".");
  }

  if (!(path instanceof Array) || path.length === 0) {
    return;
  }

  path = path.slice();

  var key = path.shift();

  if (typeof object !== "object" || object === null) {
    return;
  }

  if (path.length === 0) {
    return object[key];
  }

  if (path.length) {
    return get(object[key], path);
  }
};

Comments

1

Although not a generic library, it seems that CasperJS has something of this kind with its utils.getPropertyPath function.

/**
 * Retrieves the value of an Object foreign property using a dot-separated
 * path string.
 *
 * Beware, this function doesn't handle object key names containing a dot.
 *
 * @param  Object  obj   The source object
 * @param  String  path  Dot separated path, eg. "x.y.z"
 */
function getPropertyPath(obj, path) {
    if (!isObject(obj) || !isString(path)) {
        return undefined;
    }
    var value = obj;
    path.split('.').forEach(function(property) {
        if (typeof value === "object" && property in value) {
            value = value[property];
        } else {
            value = undefined;
        }
    });
    return value;
}

Edit:

I have come across implementations to solve this a couple times since, including:

  1. the getObject plugin by Ben Alman (on Github).
  2. one I rolled - see gist

Edit (2014)

I would also note the relatively new lodash.deep.

Comments

0

Here's a short ES6 implementation using reduce:

function get(obj, keyPath) {
    return keyPath
        .split(".")
        .reduce((prev, curr) => prev[curr], obj);
}

Usage:

get(cars, "bentley.suppliers.0.name") // -> "Sheffield Mines"

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.