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"  }
]
2

3 Answers 3

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)

Sign up to request clarification or add additional context in comments.

3 Comments

b.type. localeCompare(a.type) || (a.year - b.year) :) will explode on year - haven't seen that it's a number.
@DraganS Great solution! I've incorporated it into my answer.
check the updated answer. ... || (a.year - b.year)
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);
       }
    }
);

3 Comments

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: Thanks, fixed now. And I've incorporated your suggestion.
thanks @AndrewShepherd this one seems the cleanest, and readable. Also seems expandable to add more if needed in the future - ie. sorting by origin
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);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.