Search code examples
javascriptsortingobjectlodasharray-multisort

Multicriteria sorting object keys using Javascript and loadash


I am trying to sort an object by key using multiple criteria, Javascript, and Lodash. I don't have to use Lodash, but thought that would be easier. I am open to whatever will work at this point. Any help would be greatly appreciated!! Thank you in advance.

Here is an example object I am working with:

var object = {
  "148295945589000200_149099156236000910_filter": "some value",
  "148295945589000200_149099156236000910_filtertype": "some_value",
  "148295945589000200_filter": "asdf",
  "149098531921500909_source": "js.content_ids",
  "149098531921500909_filtertype": "some_value",
  "148295944269000199_settovar": "",
  "148295945589000200_149098530604100908_source": "js.content_type",
  "148295945589000200_filtertype": "defined",
  "148295945589000200_source": "js.content_type",
  "148295944269000199_settotext": "adsf",
  "148295945589000200_149098530604100908_filter": "some value",
  "149099546962400202_149099548423700203_filtertype": "some_value",
  "149099546962400202_149099548423700203_filter": "some value",
  "148295945589000200_149098530604100908_filtertype": "some_value",
  "148295944269000199_setoption": "text",
  "148295944269000199_set": "js.content_name",
  "149099546962400202_source": "js.acme_id",
  "149099546962400202_149099548423700203_source": "js.acme_page",
  "149099546962400202_filter": "",
  "149098531921500909_filter": "some value",
  "149099546962400202_filtertype": "defined",
  "148295945589000200_149099156236000910_source": "js.content_name"
}

This is what I am trying to do:

Sort the object based on the first 18 numbers in each key. If there is only one block of numbers, list that first, then list the keys with a second block of numbers in the series and in order. Then, sort by the remaining alphabetical portion of the key.

Expected output:

var object = {
  "148295944269000199_set": "js.content_name",
  "148295944269000199_setoption": "text",
  "148295944269000199_settotext": "adsf",
  "148295944269000199_settovar": "",
  "148295945589000200_filter": "asdf",
  "148295945589000200_filtertype": "defined",
  "148295945589000200_source": "js.content_type",
  "148295945589000200_149098530604100908_filter": "some value",
  "148295945589000200_149098530604100908_filtertype": "some_value",
  "148295945589000200_149098530604100908_source": "js.content_type",
  "148295945589000200_149099156236000910_filter": "some text",
  "148295945589000200_149099156236000910_filtertype": "some_value",
  "148295945589000200_149099156236000910_source": "js.content_name"
  "149098531921500909_filter": "some value",
  "149098531921500909_filtertype": "contains_ignore_case",
  "149098531921500909_source": "js.content_ids",
  "149099546962400202_filter": "",
  "149099546962400202_filtertype": "defined",
  "149099546962400202_source": "js.acme_page",
  "149099546962400202_149099548423700203_filter": "some value",
  "149099546962400202_149099548423700203_filtertype": "some_value",
  "149099546962400202_149099548423700203_source": "js.acme_id"
}

Here are some of the things I have tried:

(1) Tried to add to an array and sort

var array = Object.keys(object).sort(function(a, b) {
  return a.localeCompare(b);
})

var newObject = {};
array.forEach(function(key) {
  newObject[key] = object[key];
});
JSON.stringify(object, null, 2);

(2) Tried removing underscores to sort

var newObject = {};
Object.keys(object).forEach(function(key) {
  newKey = key.replace(/_/g, "");
  newObject[newKey] = object[key];
});

var array = Object.keys(newObject).sort(function(a, b) {
  return a.localeCompare(b);
})

var newestObject = {};
array.forEach(function(key) {
  newestObject[key] = newObject[key];
});
JSON.stringify(object, null, 2);

(3) Tried to convert to multidimensional array using lodash (called with "_") and use multi criteria sorting - I think I am the closest with this one, maybe. This requires lodash to work -https://raw.githubusercontent.com/lodash/lodash/4.17.4/dist/lodash.core.js

var sortable = _.toPairs(object);
var array = sortable.sort(function(a, b) {
  // check if b[0].substring(19,37) is a number first?
  if (a[0].substring(0, 18) === b[0].substring(0, 18)) {
    var x = a[1].substring(0, 18),
      y = b[1].substring(0, 18);
    return x < y ? -1 : x > y ? 1 : 0;
  }
  return a[0].substring(0, 18) - b[0].substring(0, 18);
});
var output = _.fromPairs(array);
JSON.stringify(output, null, 2);


Solution

  • We can solve this by:

    1. Converting the object into a collection of key-value pairs using lodash#toPairs.
    2. Sort the collection by key using lodash#sortBy:
      • split the key string by a '_' delimiter and get it's length.
      • replace the first occurrence of '_' character in the key string with the length of the split.
    3. Use lodash#fromPairs to convert the sorted collection into an object.

    var result = _(object)
      .toPairs()
      .sortBy(function(pair) {
        return pair[0].replace(/_/, pair[0].split('_').length);
      })
      .fromPairs()
      .value();
    

    var object = {
      "148295945589000200_149099156236000910_filter": "some value",
      "148295945589000200_149099156236000910_filtertype": "some_value",
      "148295945589000200_filter": "asdf",
      "149098531921500909_source": "js.content_ids",
      "149098531921500909_filtertype": "some_value",
      "148295944269000199_settovar": "",
      "148295945589000200_149098530604100908_source": "js.content_type",
      "148295945589000200_filtertype": "defined",
      "148295945589000200_source": "js.content_type",
      "148295944269000199_settotext": "adsf",
      "148295945589000200_149098530604100908_filter": "some value",
      "149099546962400202_149099548423700203_filtertype": "some_value",
      "149099546962400202_149099548423700203_filter": "some value",
      "148295945589000200_149098530604100908_filtertype": "some_value",
      "148295944269000199_setoption": "text",
      "148295944269000199_set": "js.content_name",
      "149099546962400202_source": "js.acme_id",
      "149099546962400202_149099548423700203_source": "js.acme_page",
      "149099546962400202_filter": "",
      "149098531921500909_filter": "some value",
      "149099546962400202_filtertype": "defined",
      "148295945589000200_149099156236000910_source": "js.content_name"
    };
    
    var result = _(object)
      .toPairs()
      .sortBy(function(pair) {
        return pair[0].replace(/_/, pair[0].split('_').length);
      })
      .fromPairs()
      .value();
      
    console.log(result);
    body > div { min-height: 100%; top: 0; }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>

    Alternatively, you can solve this without using lodash:

    function getKey(key) {
      return key.replace(/_/, key.split('_').length);
    }
    
    var result = Object.keys(object)
      .sort(function(k1, k2) {
        return getKey(k1).localeCompare(getKey(k2));
      })
      .reduce(function(acc, key) {
        acc[key] = object[key];
        return acc;
      }, {});
    

    var object = {
      "148295945589000200_149099156236000910_filter": "some value",
      "148295945589000200_149099156236000910_filtertype": "some_value",
      "148295945589000200_filter": "asdf",
      "149098531921500909_source": "js.content_ids",
      "149098531921500909_filtertype": "some_value",
      "148295944269000199_settovar": "",
      "148295945589000200_149098530604100908_source": "js.content_type",
      "148295945589000200_filtertype": "defined",
      "148295945589000200_source": "js.content_type",
      "148295944269000199_settotext": "adsf",
      "148295945589000200_149098530604100908_filter": "some value",
      "149099546962400202_149099548423700203_filtertype": "some_value",
      "149099546962400202_149099548423700203_filter": "some value",
      "148295945589000200_149098530604100908_filtertype": "some_value",
      "148295944269000199_setoption": "text",
      "148295944269000199_set": "js.content_name",
      "149099546962400202_source": "js.acme_id",
      "149099546962400202_149099548423700203_source": "js.acme_page",
      "149099546962400202_filter": "",
      "149098531921500909_filter": "some value",
      "149099546962400202_filtertype": "defined",
      "148295945589000200_149099156236000910_source": "js.content_name"
    };
    
    function getKey(key) {
      return key.replace(/_/, key.split('_').length);
    }
    
    var result = Object.keys(object)
      .sort(function(k1, k2) {
        return getKey(k1).localeCompare(getKey(k2));
      })
      .reduce(function(acc, key) {
        acc[key] = object[key];
        return acc;
      }, {});
      
    console.log(result);
    body > div { min-height: 100%; top: 0; }