JVG
JVG

Reputation: 21150

Sort javascript array of objects by child properties (which may be missing)

I have a large-ish dataset (from 400 - 4,000 objects stored in an array), and I'm trying to filter them by a user-selected field.

Right now I'm using this function, found on another SO question:

var sort = function (prop, arr) {
    prop = prop.split('.');
    var len = prop.length;

    arr.sort(function (a, b) {
        var i = 0;
        while( i < len ) {
            a = a[prop[i]];
            b = b[prop[i]];
            i++;
        }
        if (a < b) {
            return -1;
        } else if (a > b) {
            return 1;
        } else {
            return 0;
        }
    });
    return arr;
};

Sample data - I want to sort the objects by the friends count:

var data = [
    {
        name: 'Jim',
        friends: {
            count: 20,
            url: 'http://foo.com'
        }
    },{
        name: 'Lucy',
    },{
        name: 'Phil',
        friends: {
            count: 450,
            url: 'http://moo.com'
        }
    }
];

Notice how "Lucy" doesn't have a friends object - so when I run sort('friends.count', data);, the script breaks.

Ideally I'd like the objects which don't have the property that I'm sorting by to be put at the end of the array. Any ideas on how this can be achieved?

Upvotes: 1

Views: 3732

Answers (2)

georg
georg

Reputation: 214969

For example,

var data = [
    {
        name: 'Jim',
        friends: {
            count: 20,
            url: 'http://foo.com'
        }
    },{
        name: 'Lucy',
    },{
        name: 'Phil',
        friends: {
            count: 450,
            url: 'http://moo.com'
        }
    }
];

safeGet = function(obj, prop, defaultValue) {
  try {
    return obj[prop]
  } catch(e) {
    return defaultValue
  }
}


data.sort(function(x, y) {
  return (
    safeGet(x.friends, 'count', Infinity) - 
    safeGet(y.friends, 'count', Infinity));
});

document.write("<pre>" + JSON.stringify(data,0,3));

If the whole property chain (friends.count) is dynamic, change safeGet so that it iterates the list of props:

var data = [
    {
        name: 'Jim',
        friends: {
            count: 20,
            url: 'http://foo.com'
        }
    },{
        name: 'Lucy',
    },{
        name: 'Phil',
        friends: {
            count: 450,
            url: 'http://moo.com'
        }
    }
];

safeGet = function(obj, props, defaultValue) {
  try {
    return props.split('.').reduce(function(obj, p) {
      return obj[p];
    }, obj);
  } catch(e) {
    return defaultValue
  }
}


data.sort(function(x, y) {
  return (
    safeGet(x, 'friends.count', Infinity) - 
    safeGet(y, 'friends.count', Infinity));
});

document.write("<pre>" + JSON.stringify(data,0,3));

If you want people with no friends to go first, not last, change Infinity to -Infinity.

Upvotes: 3

zaynetro
zaynetro

Reputation: 2308

Your function can be modified to check for the existence of a property:

var sort = function (prop, arr) {
    prop = prop.split('.');
    var len = prop.length;

    arr.sort(function (a, b) {
        var i = 0;
        var key;

        while( i < len ) {
            key = prop[i];

            if(!a.hasOwnProperty(key)) return 1;
            if(!b.hasOwnProperty(key)) return -1;

            a = a[key];
            b = b[key];
            i++;
        }
        if (a < b) {
            return -1;
        } else if (a > b) {
            return 1;
        } else {
            return 0;
        }
    });
    return arr;
};

This way it will be working. I made a jsbin for the example.

@georg's answer wouldn't work with property selected dynamically.

Upvotes: 1

Related Questions