How to get same results when sorting JavaScript arrays in all browsers

carita-feliz

The problem:

1) Imagine you have following array in JSON

var data =
[
 {
 "title": "Fine Fillers Treatment and Clay De-Activation Agents",
 "topic": "PHP",
 "date" : "2014-01-25T01:32:21.170Z",
 "url": "http://www.alex-arriaga.com/session-1-cakephp-framework-for-beginners-in-spanish/"
 },
 {
 "title": "How to configure session factory to connect to a DB2 datasource with Hibernate",
 "topic": "Java",
 "date" : "2014-02-12T01:32:21.171Z",
 "url": "http://www.alex-arriaga.com/how-to-configure-session-factory-to-connect-to-a-db2-datasource-with-hibernate/"
 },
 {
 "title": "Fine Fillers Treatment and Clay De-Activation Agents",
 "topic": "PHP",
 "date" : "2014-06-25T01:32:21.172Z",
 "url": "http://www.alex-arriaga.com/session-3-cakephp-framework-for-beginners-in-spanish/"
 },
 {
 "title": "Fine Fillers Treatment and Clay De-Activation Agents",
 "topic": "PHP",
 "date" : "2014-06-23T01:32:21.173Z",
 "url": "http://www.alex-arriaga.com/session-2-cakephp-framework-for-beginners-in-spanish/"
 },
 ... // More items
];

2) Now, you want to order your input array by title (ascendant), but unfortunately you notice you have many titles repeated, well in most of the articles on the internet about how to write a custom function for ordering they point to create some code like:

function sortByTitleNotConsistentASC(itemA, itemB) {
    var valueA = itemA.title;
    var valueB = itemB.title;

	var a = valueA.trim().toLowerCase();
	var b = valueB.trim().toLowerCase();
	var r = ((a > b) ? 1 : ((a < b) ? -1 : 0));
    return r;
}

3) You believe everything is going to work, so you run your sort() by using the custom method like:

// Calling your custom ordering function
data.sort(sortByTitleNotConsistentASC);

But…. suddenly you realize that doesn’t work! Because you obtain different output in Google Chrome, Firefox and Internet Explorer! Then, you spend a lot of time trying to figure out the actual reason about that behavior, but… nothing, you can’t find anything clear 🙁

Let’s see next images with the error:

1) First one is the output in Firefox.

2) Second one corresponds to the output given by Google Chrome

As you can see, there are differences when the titles are the same (check the orange box, the indexes don’t match in both browsers).

sort-firefox sort-not-consistent-chrome

Here is the thing: when two or more strings are found in the same array and you apply a sort function, there is no guarantee that is going to work… why? Because every single browser implements the ECMAScript standards in a different way.

Chrome’s javascript sort function behaves more like the ECMA standards than the sort function implemented in other browsers (like Firefox, IE, etc), which try to maintain some backwards compatibility with legacy javascript code.

From: Inderpreet Singh.

 

Now, this is an interesting note from Mozilla Developers Network.

If compareFunction is supplied, the array elements are sorted according to the return value of the compare function. If a and b are two elements being compared, then:

If compareFunction(a, b) is less than 0, sort a to a lower index than b, i.e. a comes first.
If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different elements. Note: the ECMAscript standard does not guarantee this behaviour, and thus not all browsers (e.g. Mozilla versions dating back to at least 2003) respect this.
If compareFunction(a, b) is greater than 0, sort b to a lower index than a.
compareFunction(a, b) must always return the same value when given a specific pair of elements a and b as its two arguments. If inconsistent results are returned then the sort order is undefined

The solution

The solution I propose is to add an unique key per item in order to compare that key in our custom method in case of any string is equals to other. Let’s do that, we need to modify our original array to have something like:

var data =
[
    {
        "key" : 0, // This key MUST be unique
        "title": "Fine Fillers Treatment and Clay De-Activation Agents",
        "topic": "PHP",
        "date" : "2014-01-25T01:32:21.170Z",
        "url": "http://www.alex-arriaga.com/session-1-cakephp-framework-for-beginners-in-spanish/"
    },
    {
        "key" : 1, // This key MUST be unique
        "title": "How to configure session factory to connect to a DB2 datasource with Hibernate",
        "topic": "Java",
        "date" : "2014-02-12T01:32:21.171Z",
        "url": "http://www.alex-arriaga.com/how-to-configure-session-factory-to-connect-to-a-db2-datasource-with-hibernate/"
    },
    {
        "key" : 2, // This key MUST be unique
        "title": "Fine Fillers Treatment and Clay De-Activation Agents",
        "topic": "PHP",
        "date" : "2014-06-25T01:32:21.172Z",
        "url": "http://www.alex-arriaga.com/session-3-cakephp-framework-for-beginners-in-spanish/"
    },
    {
        "key" : 10, // This key MUST be unique
        "title": "Fine Fillers Treatment and Clay De-Activation Agents",
        "topic": "PHP",
        "date" : "2014-06-23T01:32:21.173Z",
        "url": "http://www.alex-arriaga.com/session-2-cakephp-framework-for-beginners-in-spanish/"
    },
    {
        "key" : 11, // This key MUST be unique
        "title": "Session: CakePHP Framework for beginners (in spanish)",
        "topic": "PHP",
        "date" : "2014-06-26T01:32:21.174Z",
        "url": "http://www.alex-arriaga.com/session-4-cakephp-framework-for-beginners-in-spanish/"
    },
    ... // More items with key
];

 Rules to add the unique key

  • The obvious one is this key MUST unique.
  • We can use the position of the item in the original array or any other thing.
  • I would recommend to use a number (integer works perfectly). This number can be generated with a more complex function (e.g. using an encoding in another numeric base, like 264).

 The new function for stable sorting

function sortByTitleConsistentASC(itemA, itemB) {
	var valueA = itemA.title;
	var valueB = itemB.title;

	var a = valueA.trim().toLowerCase();
	var b = valueB.trim().toLowerCase();
	var r = ((a > b) ? 1 : ((a < b) ? -1 : 0));
	if(r === 0){
		// Next line makes the magic :)
		r = (typeof itemA.key !== 'undefined' && typeof itemB.key !== 'undefined')?
			itemA.key - itemB.key : 0;
	}
    return r;
}

With this new implementation the outputs are the same for both browsers (even I tested on Internet Explorer 8+).

sort-consistent-firefoxsort-consistent-chrome

Wait a minute… how about ordering by date?

Well, it is basically the same, we only need to add a parsing step (I would recommend to use moment.js) to not compare dates as string but as JavaScript Date objects.

function sortByDateConsistentASC(itemA, itemB) {
	var valueA = itemA.date;
	var valueB = itemB.date;

	// 1. Adding parsing of strings to Date
	// Using moment.js, get last version of it from: http://momentjs.com/
	// Please be sure about using a valid ISO 8601 format:
	// "Deprecation warning: moment construction falls back to js Date.
	// This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info."
	var a = moment(valueA);
	var b = moment(valueB);
	var r = 0;

	// 2. Comparing two Dates (have to be valid dates)
	if (a.isValid() && b.isValid()) {
		r = ((a.valueOf() > b.valueOf()) ? 1 : ((a.valueOf() < b.valueOf()) ? -1 : 0));
	}

	// 3. In case of dates are equal apply same logic with the unique key to provide the stable sorting
	if(r === 0){
		r = (typeof itemA.key !== 'undefined' && typeof itemB.key !== 'undefined')?
			itemA.key - itemB.key : 0;
	}
    return r;
}

 Rules to parse/use dates

  • Use an ISO 8601 format, e.g: “2014-01-25T01:32:21.170Z”, in this way you will be sure moment.js will be able to parse the string into a Date object.
  • Compare dates in milliseconds (in moment.js milliseconds can be obtained with valueOf() function).
  • Always check is the date was parsed correctly (in moment.js use isValid() function)

 

I hope you find this information useful for your own implementation, you can find this code in my Github account.

And remember be happy with your code!

6 comments on “How to get same results when sorting JavaScript arrays in all browsers”

Leave a Reply

Your email address will not be published. Required fields are marked *