Is it possible to pass a custom comparator to lodash's sortBy function?

10,394

Solution 1

You can use lodash mixin's

_.mixin({
    sortWith : function(arr, customFn) {
        return _.map(arr).sort(customFn)
    }
}); 

You can now do

_.sortWith(array, function(a, b) {
   //custom function that returns either -1, 0, or 1 if a is <, ==, or > than b
});

You can now chain this like:

_.chain(myObject)
    .get('some_array_property')
    .sortWith(function(a, b) {
        //determine if a <=> b 
     })
    .value();

Internally, sortWith maps the array to a new array so that it doesn't modify the array passed into it and uses the native sort() method.

Solution 2

No, unfortunately this is not currently possible.

A workaround is to use the iteratees function to map the values to something the standard comparator will sort correctly. This is however almost never practical.

It's also asked for here https://github.com/lodash/lodash/issues/246, but no response from the author.

Solution 3

As said by other answers, you cannot pass in a comparator to _.sortBy like in Array.prototype.sort().

One workaround would be to add a new calculated property to the objects which would be the ordering and then use _.sortBy on that.

So if you had a list of objects like [{name: "hello there"}, {name: "world"}] but you wanted to sort them by name length, you could do:

_(arr)
//augment each object with a calculated `order` property
.map(obj => ({...obj, order: obj.name.length}))
.sortBy('order')
.value()

Result: [{name: "world", order: 5}, {name: "hello there", order: 11}]

Solution 4

This is enough for simple ordering based on finite values.

const MAP = {
    BRONZE: 1,
    SILVER: 2,
    GOLD: 3,
    PLATINUM: 4,
}

const DATA = [
    { name: 'A', type: 'SILVER' },
    { name: 'B', type: 'BRONZE' },
    { name: 'C', type: 'PLATINUM' },
    { name: 'F', type: 'SILVER' },
    { name: 'G', type: 'GOLD' },
    { name: 'H', type: 'BRONZE' },
]

_.sortBy(DATA, (item) => MAP[item.type])

Result:

[
    {"name":"B","type":"BRONZE"},
    {"name":"H","type":"BRONZE"},
    {"name":"A","type":"SILVER"},
    {"name":"F","type":"SILVER"},
    {"name":"G","type":"GOLD"},
    {"name":"C","type":"PLATINUM"}
]

Solution 5

actually iteratees can be mapping for the return result:

const users = [
  { 'user': 'fred',   'age': 48 },
  { 'user': 'barney', 'age': 36 },
  { 'user': 'fred',   'age': 40 },
  { 'user': 'barney', 'age': 34 }
];

_.sortBy(users, [function(o) { return o.user; }]);
// output: objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]

or iteratees can be normal js sort function like i made in the below example to sort array of objects to sort cards when cardsStatus === 'NORI' so card should be on top of array

const cardsBeforeSort = [
              {
                "cardStatus": "NORM",
                "consumedLimit": 0,
                "cardAccountSerial": "10551880",
                "cashLimit": null,
                "applePayStatus": "ELIGIBLE",
                "embossName": "Hdhh",
                "nickName": "",
                "aan": "123",
                "balance": -9,
                "key": "405433******8106"
              },
              {
                "cardStatus": "NORI",
                "consumedLimit": 0,
                "cardAccountSerial": "10551908",
                "cashLimit": null,
                "applePayStatus": "ELIGIBLE",
                "embossName": "Hdhh",
                "nickName": "",
                "aan": "123",
                "balance": 1,
                "key": "405433******8382"
              },
              {
                "cardStatus": "HOLD",
                "consumedLimit": -169122.81,
                "cardAccountSerial": "10548192",
                "cashLimit": null,
                "applePayStatus": "ELIGIBLE",
                "embossName": "Hdjj",
                "nickName": "",
                "aan": "123",
                "balance": 5579.29,
                "key": "417323******3321"
              },
              {
                "cardStatus": "NORI",
                "consumedLimit": -7.74,
                "cardAccountSerial": "10549814",
                "cashLimit": null,
                "applePayStatus": "ELIGIBLE",
                "embossName": "Hdhh",
                "nickName": "",
                "aan": "123",
                "balance": 1,
                "key": "429927******1548"
              }
            ]
    
       const sortedCards = sortBy(userCards, [
          (first, second) =>
            first.cardStatus === 'NORI' ? -1 : second === 'NORI' ? 1 : 0,
        ]);

this will result in the following output:

console.log(sortedCards);
    [
          {
            "cardStatus": "NORI",
            "consumedLimit": -7.74,
            "cardAccountSerial": "10549814",
            "cashLimit": null,
            "applePayStatus": "ELIGIBLE",
            "embossName": "Hdhh",
            "nickName": "",
            "aan": "123",
            "balance": 1,
            "key": "429927******1548"
          },
          {
            "cardStatus": "NORI",
            "consumedLimit": 0,
            "cardAccountSerial": "10551908",
            "cashLimit": null,
            "applePayStatus": "ELIGIBLE",
            "embossName": "Hdhh",
            "nickName": "",
            "aan": "123",
            "balance": 1,
            "key": "405433******8382"
          },
          {
            "cardStatus": "NORM",
            "consumedLimit": 0,
            "cardAccountSerial": "10551880",
            "cashLimit": null,
            "applePayStatus": "ELIGIBLE",
            "embossName": "Hdhh",
            "nickName": "",
            "aan": "123",
            "balance": -9,
            "key": "405433******8106"
          },
          {
            "cardStatus": "HOLD",
            "consumedLimit": -169122.81,
            "cardAccountSerial": "10548192",
            "cashLimit": null,
            "applePayStatus": "ELIGIBLE",
            "embossName": "Hdjj",
            "nickName": "",
            "aan": "123",
            "balance": 5579.29,
            "key": "417323******3321"
          },
        ]

actually the benefit of using sortBy lodash function is being functional programming immutable solution because of not mutating cardsBeforeSort array

Share:
10,394

Related videos on Youtube

Daniel
Author by

Daniel

Updated on October 31, 2022

Comments

  • Daniel
    Daniel over 1 year

    For example, I want to sort with respect to Intl.Collator().compare. Is there any way to pass this comparator to be used by _.sortBy?

    • J. Titus
      J. Titus about 7 years
      What are the items that are being sorted?
    • Mike Cluck
      Mike Cluck about 7 years
      Is there a reason you are not using the native sort method?
    • Daniel
      Daniel about 7 years
      The items that are being sorted are objects with a name string field that may contain foreign language characters. --- Currently I am using the native sort doing something like arr.slice.sort((a, b) => Intl.Collator().compare(a.name, b.name)), but I was wondering if there was a cleaner way.
  • Gianmarco
    Gianmarco over 3 years
    is this a new feature? was this added in recent times? Because sortBy is already mentioned in the question but without the feature you used. If so please specify in the answer from which version of lodash this is available to complete your answer.
  • undeadborn
    undeadborn over 3 years
    Looks like it was already in earlier versions. Lodash 1.3.1