17

I want to be able to pass any javascript object containing camelCase keys through a method and return an object with underscore_case keys, mapped to the same values.

So, I have this:

var camelCased = {firstName: 'Jon', lastName: 'Smith'}

And I want a method to output this:

{first_name: 'Jon', last_name: 'Jon'}

What's the fastest way to write a method that takes any object with any number of key/value pairs and outputs the underscore_cased version of that object?

3

10 Answers 10

18

Here's your function to convert camelCase to underscored text (see the jsfiddle):

function camelToUnderscore(key) {
    return key.replace( /([A-Z])/g, "_$1").toLowerCase();
}

console.log(camelToUnderscore('helloWorldWhatsUp'));

Then you can just loop (see the other jsfiddle):

var original = {
    whatsUp: 'you',
    myName: 'is Bob'
},
    newObject = {};

function camelToUnderscore(key) {
    return key.replace( /([A-Z])/g, "_$1" ).toLowerCase();
}

for(var camel in original) {
    newObject[camelToUnderscore(camel)] = original[camel];
}

console.log(newObject);
Sign up to request clarification or add additional context in comments.

3 Comments

Why not key.replace( /([A-Z])/g, "_$1" ) and get rid of the .split(' ').join('_') on result?
@NoahFreitas, yeah good point, that's definitely a more concise way. The OP would just need to remember to do .toLowerCase
@NoahFreitas, great suggestion thanks, I updated my answer accordingly.
8

If you have an object with children objects, you can use recursion and change all properties:

function camelCaseKeysToUnderscore(obj){
    if (typeof(obj) != "object") return obj;

    for(var oldName in obj){

        // Camel to underscore
        newName = oldName.replace(/([A-Z])/g, function($1){return "_"+$1.toLowerCase();});

        // Only process if names are different
        if (newName != oldName) {
            // Check for the old property name to avoid a ReferenceError in strict mode.
            if (obj.hasOwnProperty(oldName)) {
                obj[newName] = obj[oldName];
                delete obj[oldName];
            }
        }

        // Recursion
        if (typeof(obj[newName]) == "object") {
            obj[newName] = camelCaseKeysToUnderscore(obj[newName]);
        }

    }
    return obj;
}

So, with an object like this:

var obj = {
    userId: 20,
    userName: "John",
    subItem: {
        paramOne: "test",
        paramTwo: false
    }
}

newobj = camelCaseKeysToUnderscore(obj);

You'll get:

{
    user_id: 20,
    user_name: "John",
    sub_item: {
        param_one: "test",
        param_two: false
    }
}

Comments

4

es6 node solution below. to use, require this file, then pass object you want converted into the function and it will return the camelcased / snakecased copy of the object.

const snakecase = require('lodash.snakecase');

const traverseObj = (obj) => {
  const traverseArr = (arr) => {
    arr.forEach((v) => {
      if (v) {
        if (v.constructor === Object) {
          traverseObj(v);
        } else if (v.constructor === Array) {
          traverseArr(v);
        }
      }
    });
  };

  Object.keys(obj).forEach((k) => {
    if (obj[k]) {
      if (obj[k].constructor === Object) {
        traverseObj(obj[k]);
      } else if (obj[k].constructor === Array) {
        traverseArr(obj[k]);
      }
    }

    const sck = snakecase(k);
    if (sck !== k) {
      obj[sck] = obj[k];
      delete obj[k];
    }
  });
};

module.exports = (o) => {
  if (!o || o.constructor !== Object) return o;

  const obj = Object.assign({}, o);

  traverseObj(obj);

  return obj;
};

Comments

2

Came across this exact problem when working between JS & python/ruby objects. I noticed the accepted solution is using for in which will throw eslint error messages at you ref: https://github.com/airbnb/javascript/issues/851 which alludes to rule 11.1 re: use of pure functions rather than side effects ref:https://github.com/airbnb/javascript#iterators--nope

To that end, figured i'd share the below which passed the said rules.

import { snakeCase } from 'lodash'; // or use the regex in the accepted answer

camelCase = obj => {
  const camelCaseObj = {};
  for (const key of Object.keys(obj)){
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
     camelCaseObj[snakeCase(key)] = obj[key];
    }
  }

  return camelCaseObj;
};

Comments

2

Marcos Dimitrio posted above with his conversion function, which works but is not a pure function as it changes the original object passed in, which may be an undesireable side effect. Below returns a new object that doesn't modify the original.

export function camelCaseKeysToSnake(obj){
  if (typeof(obj) != "object") return obj;
  let newObj = {...obj}
  for(var oldName in newObj){

      // Camel to underscore
      let newName = oldName.replace(/([A-Z])/g, function($1){return "_"+$1.toLowerCase();});

      // Only process if names are different
      if (newName != oldName) {
          // Check for the old property name to avoid a ReferenceError in strict mode.
          if (newObj.hasOwnProperty(oldName)) {
              newObj[newName] = newObj[oldName];
              delete newObj[oldName];
          }
      }

      // Recursion
      if (typeof(newObj[newName]) == "object") {
          newObj[newName] = camelCaseKeysToSnake(newObj[newName]);
      }
  }
  return newObj;
} 

1 Comment

This solution may have issue if attribute something like NameNAMEName.
0

this library does exactly that: case-converter It converts snake_case to camelCase and vice versa

  const caseConverter = require('case-converter')

  const snakeCase = {
    an_object: {
      nested_string: 'nested content',
      nested_array: [{ an_object: 'something' }]
    },
    an_array: [
      { zero_index: 0 },
      { one_index: 1 }
    ]
  }

  const camelCase = caseConverter.toCamelCase(snakeCase);

  console.log(camelCase)
  /*
    {
      anObject: {
        nestedString: 'nested content',
        nestedArray: [{ anObject: 'something' }]
      },
      anArray: [
        { zeroIndex: 0 },
        { oneIndex: 1 }
      ]
    }
  */

2 Comments

the link to repo is dead, I have also find npm package, but it is marked as deprecated and message says to use @travelperksl/case-converter which has also link to dead repo and seems to be not maintained, version of package is 0.0.0-development. Meh.. 🤷
No longer useful
0

following what's suggested above, case-converter library is deprectaed, use snakecase-keys instead - https://github.com/bendrucker/snakecase-keys

supports also nested objects & exclusions.

Comments

0

Any of the above snakeCase functions can be used in a reduce function as well:

const snakeCase = [lodash / case-converter / homebrew]

const snakeCasedObject = Object.keys(obj).reduce((result, key) => ({
      ...result,
      [snakeCase(key)]: obj[key],
    }), {})

Comments

0

You can try let key = 'AttributeKEYName'; let newKey = key.replaceAll(/([A-Z]+)([A-Z][a-z])/g, '$1_$2').replaceAll(/([a-z\d])([A-Z])/g, '$1_$2').toLowerCase();

Comments

-1

jsfiddle

//This function will rename one property to another in place
Object.prototype.renameProperty = function (oldName, newName) {
     // Do nothing if the names are the same
     if (oldName == newName) {
         return this;
     }
    // Check for the old property name to avoid a ReferenceError in strict mode.
    if (this.hasOwnProperty(oldName)) {
        this[newName] = this[oldName];
        delete this[oldName];
    }
    return this;
};

//rename this to something like camelCase to snakeCase
function doStuff(object) {
    for (var property in object) {
        if (object.hasOwnProperty(property)) {
            var r = property.replace(/([A-Z])/, function(v) { return '_' + v.toLowerCase(); });
            console.log(object);
            object.renameProperty(property, r);
            console.log(object);
        }
    }
}

//example object
var camelCased = {firstName: 'Jon', lastName: 'Smith'};
doStuff(camelCased);

Note: remember to remove any and all console.logs as they aren't needed for production code

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.