Sort the array of objects by number of keys (JavaScript)

advertisements

Hi all I have an array of objects (20 of them) that I've placed in this format to make columns in an Angular Project (note there are objects in objects). I want to sort them by object with most keys (inside each object in array), to object with least number keys inside object in array) so that when they are displayed in columns it makes the most sense.

I'm mutating a navbar variable to use for a clickable advanced search. Thanks for the help as this is my first big project as a new developer.

var clickableFilters = [
  {"State": {
    "0": [{value: "liquid"}, {count: 30}]
    }
  },
  {"Country": {
    "0": [{value: "USA"}, {count: 50}]
    "1": [{value: "Nigeria"}, {count: 90}]
    }
  },
  {"Color": {
    "0": [{value: "blue"}, {count: 30}]
    "1": [{value: "purple"}, {count: 50}]
    "2": [{value: "green"}, {count: 100}]
    }
  }
 ]

How do I sort the objects by number of Keys (keys in inner object) so that it ends up (in JavaScript)

 [{"Color": {}}, {"Country": {}}, {"State": {}}]


Solution 1

Using a custom comparator for Array.prototype.sort does what you need it to do

let data = [{a:1,b:2,c:3}, {a:1}, {a:1,b:2,c:3,d:4}, {a:1,b:2}]

data.sort((a,b) => Object.keys(a).length - Object.keys(b).length)

console.log(data)

Solution 2

If your data is nested, the child data should be attached to a property of a known name

let data = [
  {foo: 'W', bar: {a:1,b:2,c:3}},
  {foo: 'X', bar: {a:1}},
  {foo: 'Y', bar: {a:1,b:2,c:3,d:4}},
  {foo: 'Z', bar: {a:1,b:2}}
]

let keyToSort = "bar";
data.sort((a,b) => Object.keys(a[keyToSort]).length - Object.keys(b[keyToSort]).length)

console.log(data)

Traps and pitfalls

On the other hand, if it is guaranteed to always have exactly one key (perhaps with an unknown/dynamic name), you could write

Object.keys(a[Object.keys(a)[0]]).length

This however is obviously very ugly and error prone (what if it does have more keys - or none at all). If you have control over the data structure, you should think about refactoring it, since an Object with only one key makes not much sense - you could as well just drop one nesting level.


It's your future

You should be in the habit of battling complexity — whenever it rears its stubborn head, grasp your staff and exert an equally stubborn force back upon it.

The first solution above appears somewhat manageable, but the second one starts to get pretty thick. If you break your solution down into tiny reusable parts, you can keep complexity at bay with relative ease.

const ascComparator = (a,b) => a < b ? -1 : a > b ? 1 : 0

// use this if you want to sort data descending instead of ascending
const descComparator = (a,b) => ascComparator(b,a)

const prop = x => y => y[x]

const len = prop('length')

const comp = f => g => x => f (g (x))

const keylen = comp (len) (Object.keys)

let data = [
  {foo: 'W', bar: {a:1,b:2,c:3}},
  {foo: 'X', bar: {a:1}},
  {foo: 'Y', bar: {a:1,b:2,c:3,d:4}},
  {foo: 'Z', bar: {a:1,b:2}}
]

// same as solution #2 but much more descriptive
// "sort ascending using keylen of a.bar compared to keylen of b.bar"
data.sort((a,b) => ascComparator(keylen(a.bar), keylen(b.bar)))

console.log(data)

Breaking complexity down is a way of investing in your future. Once you wrap a bit of complexity up in its own procedure, it will always be at your disposal at a later time. Smaller procedures are also easier to test.

Each of the procedures above ascComparator, prop, len, comp, and keylen have immediately apparent intent. You can revisit these at any time and understand them with ease. And as a result of employing them, it makes your sort that much easier to read/write too.