kouroshstst
kouroshstst

Reputation: 63

Sort array of objects of objects in javascript

I want to sort the below array by the "name" that is inside "user" object

 var myArr = [
 {"id":1,"user":{"name":"allen","id":101}},
 {"id":2,"user":{"name":"martin","id":102}}
]

how can I do this?

I have a method to sort array of objects but I can't use it for array of objects of objects

this is the method:

function dynamicSort(property) {
    var sortOrder = 1;
    if (property[0] === "-") {
        sortOrder = -1;
        property = property.substr(1);
    }
    return function (a, b) {
            var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
        return result * sortOrder;
    }
}

then I can sort using this:

myArr.sort(dynamicSort("id"));

Upvotes: 5

Views: 231

Answers (6)

kenS
kenS

Reputation: 394

Check out this SO answer for an answer to a fundamentally similar question.

Since the function in that answer is called differently than yours, with the array to be sorted passed in as a parameter, you could refactor it to be called in the same way as your existing dynamicSort function as follows:

function dynamicSort(property) {
    var sortOrder = 1;
    if (property[0] === "-") {
        sortOrder = -1;
        property = property.substr(1);
    }
    var prop = property.split('.');
    var len = prop.length;

    return function (a, b) {
        var i = 0;
        while( i < len ) { a = a[prop[i]]; b = b[prop[i]]; i++; }
        var result = (a < b) ? -1 : (a > b) ? 1 : 0;
        return result * sortOrder;
    }
}

You could then call it like so: myArr.sort(this.dynamicSort("user.name")).

Here is a working snippet to demonstrate:

function dynamicSort(property) {
    var sortOrder = 1;
    if (property[0] === "-") {
        sortOrder = -1;
        property = property.substr(1);
    }
	var prop = property.split('.');
    var len = prop.length;

    return function (a, b) {
		var i = 0;
        while( i < len ) { a = a[prop[i]]; b = b[prop[i]]; i++; }
        var result = (a < b) ? -1 : (a > b) ? 1 : 0;
        return result * sortOrder;
    }
}

var myArr = [
	{"id":1,"user":{"name":"allen","id":101}},
	{"id":2,"user":{"name":"martin","id":102}},
	{"id":3,"user":{"name":"beth","id":103}},
];

console.log(myArr.sort(this.dynamicSort("user.name"))); //expected output: [{id:1, user:{name:"allen",...}}, {id:3, user:{name:"beth",...}}, {id:2, user:{name:"martin",...}}]

Upvotes: 0

Luca Kiebel
Luca Kiebel

Reputation: 10096

Edit:

If you are experiencing problems because of periods in your key names, this approach may be better suited as a solution. The path just has to either start as a bracket notation accessor or with a dot:

function dynamicSort(property, order) {
  order||(order=1);
  const getter = new Function("obj", "return obj" + property + ";");
  return function(a, b) {
    var result = (getter(a) < getter(b)) ? -1 : (getter(a) > getter(b)) ? 1 : 0;
    return result * order;
  }
}

var myArr = [{
    "id": 1,
    "user": {
      "name": "allen",
      "id": 101
    }
  },
  {
    "id": 2,
    "user": {
      "name": "martin",
      "id": 102
    }
  },
  {
    "id": 3,
    "user": {
      "name": "barry",
      "id": 103
    }
  }
]

console.log(JSON.stringify(myArr.sort(dynamicSort(".user.name"))));


Using the Object.byString() method from this answer, you can rewrite your function to take a path to the property you want to sort by:

var myArr = [
 {"id":1,"user":{"name":"allen","id":101}},
 {"id":2,"user":{"name":"martin","id":102}},
 {"id":3,"user":{"name":"barry","id":103}}
]

console.log(JSON.stringify(myArr.sort(dynamicSort("user.name"))));


function dynamicSort(property) {
    var sortOrder = 1;
    if (property[0] === "-") {
        sortOrder = -1;
        property = property.substr(1);
    }
    return function (a, b) {
            var result = (byString(a, property) < byString(b, property)) ? -1 : (byString(a, property) > byString(b, property)) ? 1 : 0;
        return result * sortOrder;
    }
}

function byString(o, s) {
    s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
    s = s.replace(/^\./, '');           // strip a leading dot
    var a = s.split('.');
    for (var i = 0, n = a.length; i < n; ++i) {
        var k = a[i];
        if (k in o) {
            o = o[k];
        } else {
            return;
        }
    }
    return o;
}

I think it would be a little clearer and easier to use if you have the order as a second parameter, which means that your function should then more or less look like this:

function dynamicSort(property, order) {
  return function(a, b) {
    var result = (byString(a, property) < byString(b, property)) ? -1 : (byString(a, property) > byString(b, property)) ? 1 : 0;
    return result * order;
  }
}

Upvotes: 2

Blue
Blue

Reputation: 22921

I would create property as a getter function (For the complex examples. You could check if propFn is a function, use this below for the more complex ones. See this answer for checking is propFn is a function.):

var myArr = [
  {"id":1,"user":{"name":"allen","id":101}},
  {"id":2,"user":{"name":"martin","id":102}}
]

function dynamicSort(propFn, sortOrder = 1) {
    return function (a, b) {
        var result = (propFn(a) < propFn(b)) ? -1 : (propFn(a) > propFn(b)) ? 1 : 0;
        return result * sortOrder;
    }
}

console.log(myArr.sort(dynamicSort((obj) => obj.user.name)));
console.log(myArr.sort(dynamicSort((obj) => obj.user.name, -1)));

Alternatively, you can take a look at: Convert JavaScript string in dot notation into an object reference

This will give you an idea of how you can convert period notation into a nested object, but I recommend reading the disclaimer at the top.

To maintain backwards compatibility, you could use something like this below:

var myArr = [
  {"id":1,"user":{"name":"allen","id":101}},
  {"id":2,"user":{"name":"martin","id":102}}
]

function dynamicSort(propFn, sortOrder = 1) {
    if (typeof propFn === "string") {
        let prop = propFn;
        if (prop[0] === "-") {
            sortOrder = -1;
            prop = prop.substr(1);
        }

        propFn = (obj) => obj[prop];
    }
    return function (a, b) {
        var result = (propFn(a) < propFn(b)) ? -1 : (propFn(a) > propFn(b)) ? 1 : 0;
        return result * sortOrder;
    }
}

console.log(myArr.sort(dynamicSort((obj) => obj.user.name)));
console.log(myArr.sort(dynamicSort((obj) => obj.user.name, -1)));
console.log(myArr.sort(dynamicSort("id")));
console.log(myArr.sort(dynamicSort("-id")));

Upvotes: 5

Shivaji Ranaware
Shivaji Ranaware

Reputation: 169

myArr.sort(function(a, b) {
    return a.user.name.localeCompare(b.user.name);
});

Upvotes: -1

Nenad Vracar
Nenad Vracar

Reputation: 122085

You could use sort method but you first need to get nested property and for that you could pass a string and then use reduce method to get property.

 var myArr = [{"id":2,"user":{"name":"martin","id":102}}, {"id":1,"user":{"name":"allen","id":101}}]

function dynamicSort(arr, prop) {
  function getVal(obj, prop) {
    return prop.split('.').reduce((r, e) => r[e] || {}, obj)
  }

  arr.sort((a, b) => {
    let vA = getVal(a, prop);
    let vB = getVal(b, prop);
    return vA.localeCompare(vB)
  })
}

dynamicSort(myArr, "user.name")
console.log(myArr)

Upvotes: 0

Ravi kant
Ravi kant

Reputation: 89

Try this out. Works fine for me

var sortedArray = myArr.sort((a, b) => {

        const
            nameA = a.user.name.toUpperCase(),
            nameB = b.user.name.toUpperCase();

        if(nameA < nameB)
            return -1;

        if(nameA > nameB)
            return 1;

        return 0;
    });

Upvotes: -1

Related Questions