47

How can I implement a

 ORDER BY sort1 DESC, sort2 DESC

logic in an JSON array like such:

    var items = '[
          {
            "sort1": 1,
            "sort2": 3,
            "name" : "a",
          },
          {
            "sort1": 1,
            "sort2": 2,
            "name" : "b",
          },
          {
            "sort1": 2,
            "sort2": 1,
            "name" : "c",
          }
    ]';

resulting in the new order :

b,a,c
Kas Elvirov
  • 7,394
  • 4
  • 40
  • 62
coiso
  • 7,151
  • 12
  • 44
  • 63
  • 3
    Why are you putting your Array syntax inside (broken) string literal syntax? – I Hate Lazy Nov 03 '12 at 17:39
  • I'm using JSON like this, is there a better way? – coiso Nov 03 '12 at 17:44
  • 1
    Since you're assigning the Array to the `items` variable, you're already in a JavaScript environment, so it doesn't make too much sense to first represent it as JSON markup, then parse it. If that was meant to represent JSON markup being sent from the server, then a better representation is to get rid of the extra `var items = ...;` syntax, and just show it as markup with a note explaining that it's your server side markup. – I Hate Lazy Nov 03 '12 at 17:46
  • Possible duplicate of [Sort by two values prioritizing on one of them](https://stackoverflow.com/questions/4576714/sort-by-two-values-prioritizing-on-one-of-them) – Boris Yakubchik Aug 24 '19 at 14:52

3 Answers3

114

You should design your sorting function accordingly:

items.sort(function(a, b) {
  return a.sort1 - b.sort1  ||  a.sort2 - b.sort2;
});

(because || operator has lower precedence than - one, it's not necessary to use parenthesis here).

The logic is simple: if a.sort1 - b.sort1 expression evaluates to 0 (so these properties are equal), it will proceed with evaluating || expression - and return the result of a.sort2 - b.sort2.

As a sidenote, your items is actually a string literal, you have to JSON.parse to get an array:

const itemsStr = `[{
    "sort1": 1,
    "sort2": 3,
    "name": "a"
  },
  {
    "sort1": 1,
    "sort2": 2,
    "name": "b"
  },
  {
    "sort1": 2,
    "sort2": 1,
    "name": "c"
  }
]`;
const items = JSON.parse(itemsStr);
items.sort((a, b) => a.sort1 - b.sort1 || a.sort2 - b.sort2);
console.log(items);
raina77ow
  • 103,633
  • 15
  • 192
  • 229
  • using operator `-` might cause problem since the value might be strings. – xhg Nov 22 '17 at 00:28
  • I'm afraid you miss the point here, both in your comment and in your answer. Using `-` instead of `<` is the way to enforce comparing the operands as numbers, not as strings. Remember, `'12' < '3'` evaluates to `true` in JavaScript. – raina77ow Nov 22 '17 at 10:35
  • That’s exactly what I am saying. - only applies to numbers. I am not saying you are wrong. I am providing a general solution as I said in my answer. – xhg Nov 22 '17 at 10:39
  • `- only applies to numbers` - no, it's not. '12' - '3' evaluates to 9 (> 0), placing the former operand higher. '12' < '3' evaluates to `true`, reversing the order. – raina77ow Nov 22 '17 at 10:42
  • 1
    ‘12’ should be less than ‘3’. – xhg Nov 22 '17 at 10:43
  • 4
    Remember, explicit is better than implicit. If you want values to be compared as strings in _any case_, use [String.localeCompare()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) instead. `<` is ambiguous; its result depends on operands types - and type juggling, if those are different. – raina77ow Nov 22 '17 at 10:47
  • This discussion is meaningless. The title said it is `number`, then it should be number. If it is string probably you need to take care of 'abc' which `-` won't work. localeCompare() is handy BTW. – xhg Nov 22 '17 at 10:57
  • The `<` would still be the wrong operation to use, as you only want to run the second comparison for numbers that are equal in the first comparison. The `-` operand can capture all 3 cases of a comparison (>0, 0, <0) – Dogoku Nov 24 '17 at 16:26
9

You can avoid hardcoding by create a general function

function sortByMultipleKey(keys) {
    return function(a, b) {
        if (keys.length == 0) return 0; // force to equal if keys run out
        key = keys[0]; // take out the first key
        if (a[key] < b[key]) return -1; // will be 1 if DESC
        else if (a[key] > b[key]) return 1; // will be -1 if DESC
        else return sortByMultipleKey(keys.slice(1))(a, b);
    }
}

Running

items.sort(sortByMultipleKey(['sort1', 'sort2']));

will have you

[ { sort1: 1, sort2: 2, name: 'b' },
  { sort1: 1, sort2: 3, name: 'a' },
  { sort1: 2, sort2: 1, name: 'c' } ]
xhg
  • 1,850
  • 2
  • 21
  • 35
  • Sorry to say, but this implementation of generic function is way too inefficient. Not only using recursion here is dubious (you can just iterate over list of keys until comparison results in non-zero), for each 'comparison miss' there's a new instance of comparison function generated. Compare your code to [lodash implementation](https://github.com/lodash/lodash/blob/4.6.0-npm-packages/lodash.orderby/index.js#L1327), for example. – raina77ow Nov 22 '17 at 10:40
0

const itemsStr = `[{
    "sort1": 1,
    "sort2": 3,
    "name": "a"
  },
  {
    "sort1": 1,
    "sort2": 2,
    "name": "b"
  },
  {
    "sort1": 2,
    "sort2": 1,
    "name": "c"
  }
]`;
const items = JSON.parse(itemsStr);
items.sort((a, b) => a.sort1 - b.sort1 || a.sort2 - b.sort2);
console.log(items);