Two object definition refactorings from 2017

Original gist for this post

Earlier this year a colleague was tasked with modifying an application that defined a very large object (i.e., containing 20+ properties). The troubling part was that the object was entirely defined twice, first in an if-block, then in an else-block, all 20+ properties spelled out in each. That made the differences between the definitions difficult to detect just by scanning.

Here’s a mock of what we started with. (Use your imagination to replace the names with longer verbal method names like isUserLoggedIn(), userHasPermissions(), hasUserPermission() and other similar near-clashes.)

var def = {}

if (someCondition) {
  def = {
    z: z,
    r: r,
    y: y,
    a: a,
    b: b,
    w: w,
    h: h,
    t: t,
    m: m,
    q: q
  }
} else if (someOtherCondition) {
  def = {
    z: z,
    y: y,
    r: l, // not really r but l
    a: a,
    q: q2, // not really q
    b: b,
    e: e,
    x: x,
          // space to indicate that 'w' has not been assigned
    h: h,
    t: t,
    m: m
  }
}

Reduce set-up logic

The first part of the solution was to collapse two if-else statements into a default definition, then when some other condition were true, modify or add properties as needed.

  var def = {
    z: z,
    r: r,
    y: y,
    a: a,
    b: b,
    w: w,
    h: h,
    t: t,
    m: m,
    q: q
  }

Now mutate on some condition, using Object.assign. For the property we remove, define it as undefined.

if (!someCondition || someOtherCondition) {
  var diff = {
    r: l,
    q: q2,
    e: e,
    x: x,
    w: undefined
  }

  Object.assign(def, diff)
}

Better. That makes the differences between each setup easier to spot.

Re-order object’s keys alphabetically

The second part involved refactoring the base object so that the properties were defined in alphabetical order, top to bottom, for easier scanning by the human eye.

“Is there some fast way to do that?” said our colleague, obviously dreading a copy-paste-modify exercise.

The ES5 Approach

This was in an ES5 code base, so first up is an ES5 function that re-writes an object in this way. Further down (way down), there’s a more direct approach if you’re working on an ES6 object with short-cut notation…

Our test object will contain reduplicate keys pointing to same-name functions, similar to the above.

Create those functions first.

var z = function(){},
    y = function(){},
    a = function(){},
    b = function(){},
    w = function(){},
    h = function(){},
    t = function(){},
    m = function(){};

We’ll use the mapping of key to key the long way

var obj = {
  z: z,
  y: y,
  a: a,
  b: b,
  w: w,
  h: h,
  t: t,
  m: m  
}

Now we’ll re-map the object with sorted keys, and print a mapping of the keys (rather than the literal object).

var test = Object.keys(obj)
  .sort()
  .reduce(function(map, key) {
      map[key] = key // just use the key as we're interested in the output, not the literal object.
      return map
    }, {})

Print it out:

console.log( test )
// Object { a: "a", b: "b", h: "h", m: "m", t: "t", w: "w", y: "y", z: "z" }

Test it with JSON.stringify.

console.log( JSON.stringify( test ) )
// {"a":"a","b":"b","h":"h","m":"m","t":"t","w":"w","y":"y","z":"z"}

Now make it copy-able as “code” with proper newlines after each entry.

var code = JSON.stringify( test )
  .replace(/\"|\'/g, '')
  .split(',')
  .join(',\n ')

Print it out and copy it.

console.log( code )

/*
{a:a,
 b:b,
 h:h,
 m:m,
 t:t,
 w:w,
 y:y,
 z:z}
 */

The ES6 approach

This approach ends up a much shorter solution.

First, use shorthand property notations to reduce the object property entries.

var obj = { z, y, a, b, w, h, t, m }

However, JSON.stringify won’t work on that.

console.log( JSON.stringify(obj) )
// literally, {}

Instead, use array.reduce directly to concatenate the keys in sorted order.

let code = Object.keys(obj)
  .sort()
  .reduce(((str, key, i, arr) => {
      let comma = i < arr.length - 1 ? ', ' : ''
      return str + key + comma
    }), '{ ' )
  .concat(' }')

Print it out.

console.log( code )
// { a, b, h, m, t, w, y, z }

Not too bad.

One thought on “Two object definition refactorings from 2017

  1. Update 2 April 2019: don’t use reduce in the second example, just use join like so:

    '{ ' + Object.keys(struct).sort().join(', ') + ' }'
    

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s