2

I'm trying to sort array of objects based on arbitrary property inside the object. I wrote the following code which works perfectly.

var customSort = function(data, sortBy, order) {
  if (!(data && sortBy)) {
    throw new Error('Specify the data source and atleast one property to sort it by');
  }
  if (!Array.isArray(data)) {
    throw new Error('Specify the data source as an array');
  }
  order = order || 'asc';

  function performSort(order, sortBy) {
    return data.sort(function(a, b) {
      var A = parse(a, sortBy);
      var B = parse(b, sortBy);
      if (A < B) {
        return order === 'asc' ? -1 : 1;
      } else if (A > B) {
        return order === 'asc' ? 1 : -1;
      } else {
        return 0;
      }
    });
  }

  function parse(data, sortBy) {
    var sortParams = sortBy.split('.');
    var latestValue = data;
    for (var i = 0; i < sortParams.length; i++) {
      latestValue = latestValue[sortParams[i]];
    }
    if (!latestValue) {
      throw new Error('Check the \'sortBy\' parameter. Unreachable parameter in \'sortBy\'');
    }
    if (typeof latestValue === 'string') {
      return latestValue.toLowerCase();
    } else {
      return latestValue;
    }
  }

  return performSort(order, sortBy);
};

var lonelyData = [{
  a: {
    b: 2
  }
}, {
  a: {
    b: 1
  }
}, {
  a: {
    b: 9
  }
}, {
  a: {
    b: 7
  }
}, {
  a: {
    b: 4
  }
}, {
  a: {
    b: 2
  }
}, {
  a: {
    b: 1
  }
}];

console.log(customSort(lonelyData, 'a.b'));

Fiddle here: JSFiddle



Now, I'm trying to use recursion to have sort by multiple properties in following manner: First, sort by first property and then within that same property, sort by second property, and so on. For keeping things simple, I'm assuming same order for all the properties, i.e. either all increasing or all decreasing.
I wrote this code:

var customSort = function(data, sortBy, order) {
  if (!(data && sortBy)) {
    throw new Error('Specify the data source and atleast one property to sort it by');
  }
  if (!Array.isArray(data)) {
    throw new Error('Specify the data source as an array');
  }
  if (!Array.isArray(sortBy)) {
    sortBy = [sortBy];
  }
  order = order || 'asc';

  function performSort(order, sortBy) {
    return data.sort(function(a, b) {
      function nestedSort() {
        var highestOrder = sortBy[0];
        var A = parse(a, highestOrder);
        var B = parse(b, highestOrder);
        if (A < B) {
          return order === 'asc' ? -1 : 1;
        } else if (A > B) {
          return order === 'asc' ? 1 : -1;
        } else {
          sortBy.shift();
          nestedSort();
        }
      }
      return nestedSort();
    });
  }

  function parse(data, sortBy) {
    var sortParams = sortBy.split('.');
    var latestValue = data;
    for (var i = 0; i < sortParams.length; i++) {
      latestValue = latestValue[sortParams[i]];
    }
    if (!latestValue) {
      throw new Error('Check the \'sortBy\' parameter. Unreachable property passed in \'sortBy\'');
    }
    if (typeof latestValue === 'string') {
      return latestValue.toLowerCase();
    } else {
      return latestValue;
    }
  }

  return performSort(order, sortBy);
};

var lonelyData = [{
  a: {
    b: 2,
    c: 'Z'
  }
}, {
  a: {
    b: 2,
    c: 'A'
  }
}, {
  a: {
    b: 9,
    c: 'Q'
  }
}, {
  a: {
    b: 7,
    c: 'L'
  }
}, {
  a: {
    b: 7,
    c: 'E'
  }
}, {
  a: {
    b: 1,
    c: 'A'
  }
}, {
  a: {
    b: 1,
    c: 'B'
  }
}];

console.log(customSort(lonelyData, ['a.b', 'a.c']));

Fiddle here: JSFiddle


Unfortunately, I couldn't make it work. What am I missing here?

2
  • A little confused here....if the sortBy is [a.b, a.c] then you want the array to be sorted numerically, and then sorted alphabetically? Commented Oct 13, 2016 at 18:58
  • @LucasKot-Zaniewski If the sortBy is [a.b, a.c] then array should be sorted numerically (based on a.b) and then for same values of numbers (a.b), array should be alphabetically sorted (based on a.c). For example, it should be like: [{a : {b: 1, c: 'A'}}, {a : {b: 1, c: 'B'}}, {a : {b: 2, c: 'A'}}, {a : {b: 2, c: 'Z'}} ....... so on] for the above test case Commented Oct 13, 2016 at 19:00

1 Answer 1

3
     sortBy.shift();
     nestedSort();

is your problem. shift does mutate the array, and drop the first item forever. The next time the comparison function is called to compare a with b, it will be ignored, and soon the array will be empty and your comparsion breaks completely.

Instead, use a simple loop:

return data.sort(function(a, b) {
  for (var i=0; i<sortBy.length; i++)
    var currentOrder = sortBy[i];
    var A = parse(a, currentOrder);
    var B = parse(b, currentOrder);
    if (A < B) {
      return order === 'asc' ? -1 : 1;
    } else if (A > B) {
      return order === 'asc' ? 1 : -1;
    }
  }
  return 0;
});
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks a lot. It works perfectly. However, is it possible recursively as well?
Sure, just recurse over i. Or recurse through the sortBy array and .slice(1) it every time to get a copy without the first element. And don't forget your base case!

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.