Ali
Ali

Reputation: 1336

sort object array based on string + numeric value in JavaScript

I'm trying to sort array using a function. But when the number is going to two digit sorting is not correct. Otherwise it's showing correct result. Please check the code.

this is the function:

var arr = [{name: "a1", value: 1},{name: "a3", value: 1},{name: "a4", value: 1},{name: "a2", value: 1}];

var arr2 = [{name: "a1", value: 1},{name: "a3", value: 1},{name: "a14", value: 1},{name: "a12", value: 1}];

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;
};
arr = sort('name', arr);
arr2 = sort('name', arr2);
console.log(arr); // it's correct
console.log(arr2); // it's not correct

In this case I'm trying to sort the array based on value of name.

Upvotes: 0

Views: 111

Answers (3)

VLAZ
VLAZ

Reputation: 28989

You can use numeric collation with String#localeCompare. It will avoid lexicographic sorting and give you proper numeric order:

var a = "10";
var b = "2";

console.log( //false - string "10" does not come after "2" because 1 < 2
  "a > b:", 
   a > b
);
console.log( //-1 - `a` comes before `b`
  "a.localeCompare(b):", 
   a.localeCompare(b)
);
console.log( // 1 - `a` comes after `b`
  "a.localeCompare(b, undefined, {numeric: true}):",
   a.localeCompare(b, undefined, {numeric: true})
);

Note that this works for strings. If you also want to sort numbers, you have two easy options:

  • Cast the numbers to strings then use the string sorting with numeric collation. It will work the same and it keeps your code simple.
  • Use an if to check for what you're comparing and apply the .localeCompare logic for strings and a different logic for numbers.

Both would work but I personally prefer the first one, since it doesn't involve maintaining different cases. Thus using it in your solution gives the correct results:

var arr = [{name: "a1", value: 1},{name: "a3", value: 1},{name: "a4", value: 1},{name: "a2", value: 1}];

var arr2 = [{name: "a1", value: 1},{name: "a3", value: 1},{name: "a14", value: 1},{name: "a12", value: 1}];

var arr3 = [{name: "a", value: 3},{name: "a", value: 1},{name: "a", value: 14},{name: "a", value: 12}];

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++;
        }
        
        //cast to string
        a = String(a);
        b = String(b);
        
        return a.localeCompare(b, undefined, {numeric: true})
    });
    return arr;
};
arr = sort('name', arr);
arr2 = sort('name', arr2);
console.log(arr); // it's correct
console.log(arr2); // it's correct

arr3 = sort('value', arr3);
console.log(arr3); // it's correct

Upvotes: 3

Code Maniac
Code Maniac

Reputation: 37755

You need to split the alphabets and digits and then compare them

var arr = [{name: "a1", value: 1},{name: "a3", value: 1},{name: "a4", value: 1},{name: "a2", value: 1}];

var arr2 = [{name: "a1", value: 1},{name: "a3", value: 1},{name: "a14", value: 1},{name: "a12", value: 1}];

var arr3 = [{name: 1, value: 1},{name: 11, value: 1},{name: 14, value: 1},{name: 12, value: 1}];
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++;
        }
        let [key1,digit1] = (""+a).split(/(?<=[a-z])(?=\d)/)
        let [key2,digit2] = (""+b).split(/(?<=[a-z])(?=\d)/)
        return (key1 - key2) || (+digit1 - +digit2)
    });
    return arr;
};
arr = sort('name', arr);
arr2 = sort('name', arr2);
arr3 = sort('name', arr3)
console.log(arr); // it's correct
console.log(arr2); // it's not correct
console.log(arr3);

Upvotes: 1

Alex
Alex

Reputation: 1849

Name property is of type string that is why sort is performed in lexicographical order(ie alphabetical order). You would need to parse name field to sort by number inside name.

Upvotes: -1

Related Questions