0

Imagine I'm given a reference to an array of similar objects, e.g. array will be the name of that array. Now I'm asked to create an array of all the values of some property that is found inside each object of that array, e.g. "user.id".

The problem is that I won't know the format of each object and where that property will reside/be nested.So "user.id" might reside in array[#].someKey (array[#].someKey["user.id"])or in array[#].someKey.someOtherKey (array[#].someKey.someOtherKey["user.id"])

Is there a function (jQuery, underscore..etc) that could create such an array ? e.g. var arrayOfUserIds = returnArray(array, "user.id");

For example, imagine that the following is an example of such an array :

var array = [
{
  "age": "13",
  "type": "publish_action",
  "tag": null,
  "timestamp": 1398931707000,
  "content": {
    "action": "publish",
    "user.id": "860",
    "user.email": "[email protected]",
    "property.id": "2149",
    "iteration_id": "15427",
    "test_id": "6063",
    "property.name" : "bloop"
}, {
  ....
}, {
  ....
}];

Based on the above, I could obviously do :

var arrayOfUserIds = [];

for (var i=0; i<array.length; i++)
{
  arrayOfUserIds.push(array[i]["content"]["user.id"]);
}

But like I said, in my case I won't know about the format of the object so I can't create such a for-loop for example.

Any ideas will be much appreciated!

Thank you!

3
  • 2
    jsfiddle.net/ErjLB Commented May 4, 2014 at 19:56
  • wow this actually did the trick in a really compact way! You should add this as an answer Commented May 4, 2014 at 20:07
  • If you don't know the format, you can do nothing but a simple recursive search. Commented May 4, 2014 at 20:52

2 Answers 2

2

If I understand correctly, each object in someArray either contains a property user.id or an object that contains user.id...or, recursively, some object that contains someArray. You want to create an array containing only the user.id properties.

An easy way to do this would be to do a recursive examination of each object in the array until user.id is located:

// get `user.id` property from an object, or sub-object
// it is assumed that there will only be one such property;
// if there are more than one, only the first one will be returned
function getUserId(o){
    if(o===null || o===undefined) return;
    if(o['user.id']) return o['user.id'];
    for(var p in o){
        if(!o.hasOwnProperty(p)) continue;
        if(typeof o[p] !== 'object') continue;
        if(o[p] === null || o[p] === undefined) continue;
        var id = getUserId(o[p]);
        if(id) return id;
    }
}

function getUserIds(arr){
    return arr.map(function(e){
        return getUserId(e);
    });
}

If you want something a little more generic, you could write a "find" method that will find all instances of a named property in an object tree:

 var find = (function(){
    function find(matches, o, prop, checkPrototypeChain){
        if(typeof o[prop] !== 'undefined') matches.push(o[prop]);
        for(var p in o){
            if(checkPrototypeChain || !o.hasOwnProperty(p)) continue;
            if(typeof o[p] !== 'object') continue;
            if(o[p] === null || o[p] === undefined) continue;
            find(matches, o[p], prop, checkPrototypeChain);
        }
    }
    return function(o, prop, checkPrototypeChain){
        var matches = [];
        find(matches, o, prop, checkPrototypeChain);
        return matches;
    }
})();

Then you could just map your array based on that:

var userIds = someArray.map(function(e){ return find(e, 'user.id'); });

Note that I'm glossing over properties that may be in the prototype chain, but in the find function, I added the ability to additionally search for properties in the prototype chain.

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

3 Comments

I see. The above actually returns Cannot read property 'user.id' of null I guess some extra evaluation is needed somewhere but it's a good starting point! Thanks a lot!
If that's the case, then you have some nulls in your array...it's easy enough to account for
Added checks for null/undefined.
0

I went with the assumption that you're only working with primitives and object/array literals. In which case, the following method (using underscore) seems to do the trick.

var testSubject = {
    mykey: 9,
    firstArray: [
        {something: 9, another: {x: 'hello', mykey: 'dude'}, mykey: 'whatever'},
        {something: 9, another: {x: 'hello', mykey: 'dude2'}, mykey: 'whatever2'},
        {
            someArray: [
                {seven: 7, mykey: 'another'},
                {hasNo: 'mykey', atAll: 'mykey'}
            ]
        }
    ],
    anObject: {beef: 'jerky', mykey: 19}
};

function getValuesForKey(subject, searchKey) {
    return _.reduce(subject, function(memo, value, key) {
        if (_.isObject(value)) {
            memo = memo.concat(getValuesForKey(value, searchKey));
        } else if (key === searchKey) {
            memo.push(value);
        }
        return memo;
    }, []);
}

console.log(getValuesForKey(testSubject, 'mykey'));
// -> [9, "dude", "whatever", "dude2", "whatever2", "another", 19] 

It only returns the list of values because they will all share the same key (i.e. the one specified). Additionally, I do believe any matching keys will be ignored if their values are not primitive (e.g. mykey: {…} or mykey: […] should be ignored). I hope it helps.

Comments

Your Answer

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