1

I was wondering if there was a better or smarter way of sorting objects in JavaScript when sorting for multiple items?

For example, if I have the following JSON:

json = [
 { "type": "car",   "year": 2007, "origin": "AUS" },
 { "type": "truck", "year": 2021, "origin": "USA" },
 { "type": "car",   "year": 2004, "origin": "CA"  },
 { "type": "bike",  "year": 2016, "origin": "UK"  },
 { "type": "truck", "year": 2020, "origin": "CA"  },
 { "type": "bike",  "year": 2000, "origin": "AUS" }
]

If I wanted to sort it by type then year how would I do that in the same .sort function?

I've currently got it doing one, then the other - but when I try to combine them it doesn't seem to output correctly:

// one after other
json.sort( ( a, b ) => {
 a = a.type.toLowerCase();
 b = b.type.toLowerCase();
 return a < b ? -1 : a > b ? 1 : 0;
})
.sort( ( a, b ) => {
 a = a.year;
 b = b.year;
 return a < b ? -1 : a > b ? 1 : 0;
});

Returning:

[
 { "type": "bike",  "year": 2000, "origin": "AUS" },
 { "type": "car",   "year": 2004, "origin": "CA"  },
 { "type": "car",   "year": 2007, "origin": "AUS" },
 { "type": "bike",  "year": 2016, "origin": "UK"  },
 { "type": "truck", "year": 2020, "origin": "USA" },
 { "type": "truck", "year": 2021, "origin": "CA"  }
]

When it should return (all the types together, then by year):

[
 { "type": "bike",  "year": 2000, "origin": "AUS" },
 { "type": "bike",  "year": 2016, "origin": "UK"  },
 { "type": "car",   "year": 2004, "origin": "CA"  },
 { "type": "car",   "year": 2007, "origin": "AUS" },
 { "type": "truck", "year": 2020, "origin": "USA" },
 { "type": "truck", "year": 2021, "origin": "CA"  }
]
Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
markb
  • 1,100
  • 1
  • 15
  • 40
  • Does this answer your question? [How to sort an array of objects by multiple fields?](https://stackoverflow.com/questions/6913512/how-to-sort-an-array-of-objects-by-multiple-fields) – Heretic Monkey Oct 02 '21 at 00:24
  • As I'm lazy I would go with _.sortBy(json, ['type', 'year']); – DraganS Oct 02 '21 at 00:35

3 Answers3

1

In the sort callback, compare the items' type property. If one is lexographically greater/smaller, sort it before/after the other item accordingly.

Otherwise, you can return the result after subtracting the two items' year property:

const arr = [
 { "type": "car",   "year": 2007, "origin": "AUS" },
 { "type": "truck", "year": 2021, "origin": "USA" },
 { "type": "car",   "year": 2004, "origin": "CA"  },
 { "type": "bike",  "year": 2016, "origin": "UK"  },
 { "type": "truck", "year": 2020, "origin": "CA"  },
 { "type": "bike",  "year": 2000, "origin": "AUS" }
]

const sorted = arr.sort((a,b) => a.type.localeCompare(b.type) || a.year - b.year)

console.log(sorted)

Credit to DraganS

If you don't want the comparison to be case-sensitive, you can set localeCompare's sensitivity option to accent:

const arr = [
 { "type": "car",   "year": 2007, "origin": "AUS" },
 { "type": "truck", "year": 2021, "origin": "USA" },
 { "type": "car",   "year": 2004, "origin": "CA"  },
 { "type": "bike",  "year": 2016, "origin": "UK"  },
 { "type": "truck", "year": 2020, "origin": "CA"  },
 { "type": "bike",  "year": 2000, "origin": "AUS" }
]

const sorted = arr.sort((a,b) => a.type.localeCompare(b.type, undefined, {sensitivity:'accent'}) || a.year - b.year)

console.log(sorted)
Spectric
  • 30,714
  • 6
  • 20
  • 43
0

If you do one sort followed by a different sort, the final sort overrides any previous sort.

Combine them into the same lambda


json.sort( ( a, b ) => {
 aType = a.type.toLowerCase();
 bType = b.type.toLowerCase();
 aYear = a.year;
 bYear = b.year;
 return aType < bType 
        ? -1 
        : aType > bType 
           ? 1 
           : aYear < bYear
              ? -1
              : aYear > bYear
                 ? 1
                 : 0;
})


Though this has gotten pretty unreadable. You can have multiple comparison functions:

let compareByType = (a, b) => {
     aType = a.type.toLowerCase();
     bType = b.type.toLowerCase();
     return (aType<bType) ? -1 : (aType>bType ? 1 : 0);
}

let compareByYear = (a,b) => {
     return a.year - b.year;
}

json.sort(
    (a, b) => {
       let s = compareByType(a, b);
       if(s !== 0) {
          return s;
       } else {
           return compareByYear(a, b);
       }
    }
);
Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205
  • aYear= a.type.toLowerCase(); is copy-paste issue. Actually, sort is checking the sign of result so usually aYear - bYear is enough. compareByType might be an overkill as there is an already comparator in string object. Pls check Spectric's solution. – DraganS Oct 02 '21 at 00:39
  • 1
    @DraganS: Thanks, fixed now. And I've incorporated your suggestion. – Andrew Shepherd Oct 02 '21 at 00:40
  • thanks @AndrewShepherd this one seems the cleanest, and readable. Also seems expandable to add more if needed in the future - ie. sorting by `origin` – markb Oct 02 '21 at 00:44
0

You can do this with one sort by first checking if the types are equal. If so, sort on the date. If not, sort on the type.

const json = [
 { "type": "car",   "year": 2007, "origin": "AUS" },
 { "type": "truck", "year": 2021, "origin": "USA" },
 { "type": "car",   "year": 2004, "origin": "CA"  },
 { "type": "bike",  "year": 2016, "origin": "UK"  },
 { "type": "truck", "year": 2020, "origin": "CA"  },
 { "type": "bike",  "year": 2000, "origin": "AUS" }
]

// one after other
json.sort( ( a, b ) => {
 a = a.type.toLowerCase();
 b = b.type.toLowerCase();
 if (a === b) {
   return a.year - b.year;
 }
 return a < b ? -1 : 1;
});

console.log(json);
Nick
  • 16,066
  • 3
  • 16
  • 32