Converting Object Keys from Snake Case to Camel Case with Javascript

I run into a situation frequently when dealing with APIs where my JavaScript frontend expects camel case object keys while APIs tend to return snake case keys. I've settled on a few utility methods for converting these that are worth sharing here.

I'm using ES6 conventions throughout the article. And so there is no confusion, this is what I mean when I talk about the different cases during this article.

camelCase
snake_case
kebab-case
PascalCase // I won't be using this here, but it's only one small step further if you want to use it

Our first stop is simply converting a string to camel case. This actually will accept either snake or kebab case strings and convert them to camel case. It won't work if you have spaces in the string, so you can't pass "space case" to it and expect to return "spaceCase." Although it would be a relatively minor change to allow this.

const toCamel = (s) => {
  return s.replace(/([-_][a-z])/ig, ($1) => {
    return $1.toUpperCase()
      .replace('-', '')
      .replace('_', '');
  });
};

There is a lot packed in there, but it's quite simple. We first do a regex replace on the string, searching for either a dash or an underscore, followed by a letter a-z. The i at the end of the regex tells it to ignore case, so we match kebab-case as well as kebab-Case, converting both to kebabCase. If, for some reason you need to treat those differently, just remove the i and your regex is case sensitive.

We wrapped our matches in parenthesis, ([-_][a-z]), which turns them into the matching group $1. If we were converting the string snake_case with this function, the variable $1 would be _c. We then use the JS built-in function toUpperCase to convert the matching substring to uppercase, and finally remove the dashes and underscores.

That's all we need to convert kebab and snake case to camel case.

Check out this part of the tutorial on JSFiddle, with some basic tests.

What About Nested Object Keys?

Often I don't have single variable names I want to convert, but an entire object of keys that are sent to me in snake case. Something like this. I threw in some kebab case just to test if it works too.

const t1 = {
  best_chili: {
    chili_ingredients: [
      'beef',
      'dried chilis',
      'fresh tomatoes',
      'cumin',
      'onions',
      'onion-powder',
      'peppers',
    ],
    chili_steps: {
      step_1: '',
      step_2: '',
    },
  },
  serves: 6,
  pairs_with: [
    {
      'french-bread': {},
    },
    {
      'rye-croutons': {},
    },
  ],
};

Your first instinct may be to simply loop over the object keys and convert them to camel using your shiny new toCamel function. That doesn't deal with nested objects, or lists of objects, however. We need something iterative, recursive, and flexible. It needs to return positively whether the values of the object are a list, another object, or another native data type.

Detecting Object Types in JavaScript

The first thing we need to do is figure out if we're dealing with an object, array, or something else. This is... surprisingly difficult. You see, in JavaScript, almost everything is an object. Except when it's not. It's pure insanity.

const a = [];
typeof a; // returns 'object'

const o = {};
typeof o; // returns 'object'

const f = function() {};
typeof f; // returns 'function'

// but wait, there's more!
f instanceof Object; // returns true

// so a function IS an object, okay...

// how about strings?
const s = '';
typeof s; // returns 'string'
s instanceof Object; // returns false

// finally something makes sense!

// surely... null... it... it can't possibly be an object, can it?
const n = null;
typeof n; // returns 'object'

// sigh

// I guess null IS an object
n instanceof Object; // returns false
n === Object(n); // returns false

// or not?

// I give up

Sadly, we can't just ask JavaScript the type of a variable and get a sensible response. So we have to figure it out ourselves.

First up, let's find out if a variable is an array. There are a few ways we could do this. They all look slightly different. This StackOverflow answer goes into detail about the performance of each, however, if you click on the actual benchmark the results seem to have changed. I wouldn't worry too much about performance as this kind of micro-optimization is almost never needed. So choose whichever one looks the best to you.

const isArray = function (a) {
  return Array.isArray(a);
};

It looks silly to create our own function for this, but I like the look of simply calling isArray(arr). And later, when JavaScript decides that Booleans are also arrays, all of my array checks exist within my own function so I can come up with some new way to test Array-ness, and I only have to refactor this one line in my code.

That was the easy one. Next we need a test to discover if an object is really an object, or something else (like an array, a function, null, a pocket watch, exactly half of an 11-pound black forest ham, or whatever else JS decides should be an object this week). My approach to this may not be the best or best performing one, but it works so far for me.

We need to

  1. Test the object is an Object.
  2. Test the object is not null.
  3. Test it is not an array.
  4. Test it is not a function.

Like this.

const isObject = function (o) {
  return o === Object(o) && !isArray(o) && typeof o !== 'function';
};

Straightforward. I mean, not as straightforward as the language we're using actually giving us a sensible way to do this, but all in all, not bad.

Converting Object Keys from Kebab and Snake Case to Camel Case

Finally, we have done what JavaScript the language seemingly could not do itself. Time to actually write our function to convert object keys to camel case.

const keysToCamel = function (o) {
  if (isObject(o)) {
    const n = {};

    Object.keys(o)
      .forEach((k) => {
        n[toCamel(k)] = keysToCamel(o[k]);
      });

    return n;
  } else if (isArray(o)) {
    return o.map((i) => {
      return keysToCamel(i);
    });
  }

  return o;
};

I'm sure there are more elegant ways to tackle this problem, and no doubt someone out there has made a contrived one-liner, but I value forthrightness more than cleverness. It's easier to understand when you return to the code months or years later.

Here's how it works: we accept an object to our function. We first test if it's an object using the method we wrote earlier. If so, we loop through all of its keys, convert the key to camel case, and recursively call our keysToCamel function on the value of each item in the object. This allows us to continue traversing deeper and deeper into our data.

If we were worried about getting too deep and falling into limbo, or into the quantum level, we could add a depth limit and track the depth between each recursive call. I don't find it necessary as rarely is my data set that large.

Next we test if the object passed to our keysToCamel function is an array. If so, we loop through it and again recursively call our function on whatever the array holds. We don't care if it's an array of strings, objects, or a mixed array, because we know the keysToCamel function will check that before doing any conversions.

If we are dealing with neither an object nor an array we simply return the value.

Note that we are never actually converting list items or other non-objects to camel case. In our example above, 'fresh tomatoes' and 'onion-powder' are included as strings in a list. They are left unchanged. The only time the function calls toCamel is on the key of an object item.

After all that, we're left with a delicious pot of camelCase chili.

{
  "bestChili": {
    "chiliIngredients": [
      "beef",
      "dried chilis",
      "fresh tomatoes",
      "cumin",
      "onions",
      "onion-powder",
      "peppers"
    ],
    "chiliSteps": {
      "step_1": "",
      "step_2": ""
    }
  },
  "serves": 6,
  "pairsWith": [
    {
      "frenchBread": {}
    },
    {
      "ryeCroutons": {}
    }
  ]
}

Here is the full thing on JSFiddle, with our test object.

Originally published on

Last updated on