25

I've got an Array of Objects I want to sort by two Properties:

  1. RemindingTimestamp
  2. ModificationTimestamp

Sorting order: desc

Sorting this Object by one Property isn't the problem, but in this case I've no idea how to get it work.

2
  • 3
    Compare the first property. If the property value is the same for two elements, only then compare the second property. (Haven't you ever sorted a deck of playing cards?) Commented Apr 14, 2012 at 13:05
  • What are the types of these properties? Numbers, strings, or dates? Commented Apr 14, 2012 at 15:25

5 Answers 5

27

Presuming the timestamps themselves sort ok (e.g. ISO8601 and same time zone), try:

myArray.sort(function(a,b) {
  var x = a.RemindingTimestamp - b.RemindingTimestamp;
  return x == 0? a.ModificationTimestamp - b.ModificationTimestamp : x;
}

Edit - response to comments

A descending sort is achieved by changing the order of subtraction, or multiplying the result by -1. Dates that don't sort because they don't subtract (e.g. 2012-04-12) can be handled by conversion to dates first, e.g.

// Convert ISO8601 date string to date object
// Assuming date is ISO8601 long format, ignores timezone
function toDate(s) {
  var bits = s.split(/[-T :]/);
  var d = new Date(bits[0], bits[1]-1, bits[2]);
  d.setHours(bits[3], bits[4], parseFloat(bits[5])); 
  return d;
}

// Source data, should end up sorted per n
var myArray = [ 
  {RemindingTimestamp: '2012-04-15T23:15:12Z', 
   ModificationTimestamp: '2012-04-15T23:15:12Z', n: 4},
  {RemindingTimestamp: '2012-04-12T23:15:12Z', 
   ModificationTimestamp: '2012-04-12T23:15:12Z', n: 1},
  {RemindingTimestamp: '2012-04-12T23:15:12Z', 
   ModificationTimestamp: '2012-04-13T23:15:12Z', n: 2},
  {RemindingTimestamp: '2012-04-12T23:15:12Z', 
   ModificationTimestamp: '2012-04-13T23:15:14Z', n: 3}
];

// Sort it
myArray.sort(function(a,b) {
  var x = toDate(a.RemindingTimestamp) - toDate(b.RemindingTimestamp);
  return x? x : toDate(a.ModificationTimestamp) - toDate(b.ModificationTimestamp);
});

// Just to show the result
function sa(o) {
  var result = [], t;
  for (var i=0; i<o.length; i++) {
    t = o[i]; 
      result.push(t.n);
  }
  alert(result);
}

sa(myArray); // 1,2,3,4

The conversion of date string to date object can be extended to handle time zone if required (for ISO8601 compliant strings only, those that use time zone abbreviations instead of the actual offset are unreliable).

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

2 Comments

@Phrogz—a descending sort is achiived by changing a - b to b - a. ISO8601 short dates sort (e.g. 20120412), long dates can be converted to date objects before subtraction.
I love this solution, wish I could do more than +1 :)
12
function compareObject(obj1, obj2){
    if(obj1.RemindingTimestamp > obj2.RemindingTimestamp)
        return - 1;
    if(obj2.RemindingTimestamp > obj1.RemindingTimestamp)
        return 1;

    // obj1.RemindingTimestamp == obj2.RemindingTimestamp

    if(obj1.ModificationTimestamp > obj2.ModificationTimestamp)
        return -1;
    if(obj2.ModificationTimestamp > obj1.ModificationTimestamp)
        return 1;

    return 0;
}

myObjects.sort(compareObject);

JSFiddle Demo

Resources:

Comments

7

Custom comparators take the form:

myArray.sort(function(a,b){
  var m1=a1.RemindingTimestamp,
      m2=a2.RemindingTimestamp,
      n1=a1.ModificationTimestamp,
      n2=a2.ModificationTimestamp;
  return m1<m2 ? -1 : m1>m2 ? 1 :
         n1<n2 ? -1 : n1>n2 ? 1 : 0;
});

For descending sort, swap the < and > (or swap 1 and -1).

While you can make your own custom comparator each time you need this, I have created a method designed explicitly for easily sorting by multiple criteria, using a Schwartzian transform (which may be faster but more memory hungry in some circumstances): http://phrogz.net/js/Array.prototype.sortBy.js

In short:

myArray.sortBy(function(obj){
  return [obj.RemindingTimestamp, obj.ModificationTimestamp];
}).reverse();

The reverse is there since you mentioned that you wanted a descending sort. If both RemindingTimestamp and ModificationTimestamp are numbers, you could alternatively do:

myArray.sortBy(function(obj){
  return [-obj.RemindingTimestamp, -obj.ModificationTimestamp];
});

Here is the code that adds sortBy to arrays:

(function(){
  // Extend Arrays in a safe, non-enumerable way
  if (typeof Object.defineProperty === 'function'){
    // Guard against IE8's broken defineProperty
    try{Object.defineProperty(Array.prototype,'sortBy',{value:sb}); }catch(e){}
  }
  // Fall back to an enumerable implementation
  if (!Array.prototype.sortBy) Array.prototype.sortBy = sb;

  function sb(f){
    for (var i=this.length;i;){
      var o = this[--i];
      this[i] = [].concat(f.call(o,o,i),o);
    }
    this.sort(function(a,b){
      for (var i=0,len=a.length;i<len;++i){
        if (a[i]!=b[i]) return a[i]<b[i]?-1:1;
      }
      return 0;
    });
    for (var i=this.length;i;){
      this[--i]=this[i][this[i].length-1];
    }
    return this;
  }
})();

Here are some more examples from the docs:

var a=[ {c:"GK",age:37}, {c:"ZK",age:13}, {c:"TK",age:14}, {c:"AK",age:13} ];

a.sortBy( function(){ return this.age } );                                  
  --> [ {c:"ZK",age:13}, {c:"AK",age:13}, {c:"TK",age:14}, {c:"GK",age:37} ]

a.sortBy( function(){ return [this.age,this.c] } );                         
  --> [ {c:"AK",age:13}, {c:"ZK",age:13}, {c:"TK",age:14}, {c:"GK",age:37} ]

a.sortBy( function(){ return -this.age } );                                 
  --> [ {c:"GK",age:37}, {c:"TK",age:14}, {c:"ZK",age:13}, {c:"AK",age:13} ]


var n=[ 1, 99, 15, "2", "100", 3, 34, "foo", "bar" ];

n.sort();
  --> [ 1, "100", 15, "2", 3, 34, 99, "bar", "foo" ]

n.sortBy( function(){ return this*1 } );
  --> [ "foo", "bar", 1, "2", 3, 15, 34, 99, "100" ]

n.sortBy( function(o){ return [typeof o,this] } );
  --> [1, 3, 15, 34, 99, "100", "2", "bar", "foo"]

n.sortBy(function(o){ return [typeof o, typeof o=="string" ? o.length : o] })
  --> [1, 3, 15, 34, 99, "2", "100", "bar", "foo"]

Note in the last example that (typeof this) happens not to be the same as (typeof o); see this post for more details.

Comments

5

Assuming that both properties are in the same sortable format, here's another way of deep sorting in ES6:

const comparingFunction = (a, b) => {
  if (a.property1 < b.property1) {
    return -1;
  }
  if (a.property1 > b.property1) {
    return 1;
  }

  if (a.property1 == b.property1) {
    if (a.property2 < b.property2) {
      return -1;
    }
    if (a.property2 > b.property2) {
      return 1;
    }
    return 0;
  }
};

myArrayOfObjects.sort(comparingFunction);

Hope it helps somebody.

Comments

0

Another way

function sortBy(ar) {
  return ar.sort((a, b) => a.RemindingTimestamp  === b.RemindingTimestamp  ?
      a.ModificationTimestamp.toString().localeCompare(b.ModificationTimestamp) :
      a.RemindingTimestamp.toString().localeCompare(b.RemindingTimestamp));
}

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.