All Articles

Immutably Rename Object Keys in Javascript

If you’re okay with mutating data, renaming an object’s key is easy.

obj = { name: 'Bobo' };
obj.somethingElse = obj.name;
delete obj.name;

If you don’t want to mutate your data, however, consider this function.

renameProp = (oldProp, newProp, { [oldProp]: old, ...others }) => ({
  [newProp]: old,
  ...others
});

What’s happening here:

  • Computed property names
  • Spread syntax
  • Rest params
  • Destructuring assignment

Let’s add a debugger and inspect.

renameProp = (oldProp, newProp, { [oldProp]: old, ...others }) => {
  debugger;

  return {
    [newProp]: old,
    ...others
  };
};

Imagine we have an object, bobo.

bobo = {
  name: 'Bobo',
  job: 'Front-End Master',
  age: 25
};

And we want to change bobo’s name to firstName, so we plug him into renameProp.

renameProp('name', 'firstName', bobo);

Our local variables are

  • oldProp: the first parameter, 'name'
  • newProp: the second parameter, 'firstName'
  • old: A computed property name based on oldProp. It’s bobo.name.
  • others: All of bobo’s other properties

Let’s dive into line 4 of our code.

{ [oldProp]: old, ...others }

Dynamically find bobo’s name

Our function’s oldProp param is ‘name’, right? And the third parameter, the object, is bobo, so typing bobo[oldProp] would return bobo.name.

The first half of line 4 uses oldProp to find bobo’s name and assigns it to a new variable, old.

Gather bobo’s other properties

Now let’s focus on line 4’s other half.

Our function must change one of bobo’s property names without mutating him, so bobo’s other properties must remain untouched. We use spread syntax to achieve this.

Spread syntax is a beautiful shorthand for gathering bobo’s other properties and assigning them a variable named others.

Let’s write a similar function to cement the concept into our heads.

getPropsWithout = (names, object) =>
  Object.keys(object)
    .filter((key) => !names.includes(key))
    .reduce(
      (newObject, currentKey) => ({
        ...newObject,
        [currentKey]: object[currentKey]
      }),
      {}
    );

Don’t think about that function too much (unless you’re feeling adventurous! 😁). Just know that it takes an array of properties to exclude from a given object. We can use it like so:

boboNoName = getPropsWithout(['name'], bobo);

Since we’re only omitting name, boboNoName is identical to our others variable that used spread syntax.

Let’s recap!

Again, our local variables are

  • oldProp: ‘name’ because it’s the first parameter
  • newProp: ‘firstName’ because it’s the second parameter
  • old: ‘Bobo’ because we dynamically assigned it using computed property names.
  • others: { job: ‘Front-End Master’, age: 25 } because we used spread syntax to dynamically assign it. (Check out my spread article! 😁)

Now let’s focus on the return statement.

return {
  [newProp]: old,
  ...others
};

Computed property names are being leveraged once again. We dynamically create a new object and set its firstName to old. It’s like writing

// remember,
// old = 'Bobo'
// newProp = 'firstName'

newBobo = {};

newBobo[newProp] = old;
// OR
newBobo.firstName = old;

Finally, we merge others with the new object. If you’re familiar with Object.assign, it’s just like writing

return Object.assign({}, newBobo, others);

Now bobo has a firstName!

And the original bobo is left unaffected.