Convert different strings to snake_case in Javascript

Viewed 11764

I know that we have a question similar to this but not quite the same. I'm trying to make my function work which takes in a string as an argument and converts it to snake_case . It works most of the time with all the fancy !?<>= characters but there is one case that it can't convert and its camelCase .

It fails when I'm passing strings like snakeCase. It returns snakecase instead of snake_case.

I tried to implement it but I ended up just messing it up even more..

Can I have some help please?

my code:

const snakeCase = string => {
    string = string.replace(/\W+/g, " ").toLowerCase().split(' ').join('_');

    if (string.charAt(string.length - 1) === '_') {
        return string.substring(0, string.length - 1);
    }

    return string;
}
6 Answers

You need to be able to detect the points at which an upper-case letter is in the string following another letter (that is, not following a space). You can do this with a regular expression, before you call toLowerCase on the input string:

\B(?=[A-Z])

In other words, a non-word boundary, followed by an upper case character. Split on either the above, or on a literal space, then .map the resulting array to lower case, and then you can join by underscores:

const snakeCase = string => {
    return string.replace(/\W+/g, " ")
      .split(/ |\B(?=[A-Z])/)
      .map(word => word.toLowerCase())
      .join('_');
};

console.log(snakeCase('snakeCase'));

Let's try that again Stan... this should do snake_case while realising that CamelCASECapitals = camel_case_capitals. It's actually the accept answer with a pre-filter.

let splitCaps = string => string
    .replace(/([a-z])([A-Z]+)/g, (m, s1, s2) => s1 + ' ' + s2)
    .replace(/([A-Z])([A-Z]+)([^a-zA-Z0-9]*)$/, (m, s1, s2, s3) => s1 + s2.toLowerCase() + s3)
    .replace(/([A-Z]+)([A-Z][a-z])/g, 
        (m, s1, s2) => s1.toLowerCase() + ' ' + s2);
let snakeCase = string =>
    splitCaps(string)
        .replace(/\W+/g, " ")
        .split(/ |\B(?=[A-Z])/)
        .map(word => word.toLowerCase())
        .join('_');
> a = ['CamelCASERules', 'IndexID', 'CamelCASE', 'aID', 
       'theIDForUSGovAndDOD', 'TheID_', '_IDOne']

> _.map(a, snakeCase)

['camel_case_rules', 'index_id', 'camel_case', 'a_id', 'the_id_for_us_gov_and_dod', 
 'the_id_', '_id_one']

// And for the curious, here's the output from the pre-filter:

> _.map(a, splitCaps)

['Camel case Rules', 'Index Id', 'Camel Case', 'a Id', 'the id For us Gov And Dod', 
 'The Id_', '_id One']

Suppose the string is Hello World? and you want the returned value as hello_world? (with the character, then follow the below code)

const snakeCase = (string) => {
  return string.replace(/\d+/g, ' ')
    .split(/ |\B(?=[A-Z])/)
    .map((word) => word.toLowerCase())
    .join('_');
};

Example

snakeCase('Hello World?')
// "hello_world?"

snakeCase('Hello & World')
// "hello_&_world"

EDIT: It turns out this answer isn’t fool proof. The fool, being me ;-) Please check out a better one by Orwellophile here: https://stackoverflow.com/a/69878219/5377276

——

I think this one should cover all the bases

It was inspired by @h0r53's answer to the accepted answer. However it evolved into a more complete function, as it will convert any string, camelCase, kebab-case or otherwise into snake_case the way you'd expect it, containing only a-z and 0-9 characters, usable for function and variable names:

convert_to_snake_case(string) {
    return string.charAt(0).toLowerCase() + string.slice(1) // lowercase the first character
      .replace(/\W+/g, " ") // Remove all excess white space and replace & , . etc.
      .replace(/([a-z])([A-Z])([a-z])/g, "$1 $2$3") // Put a space at the position of a camelCase -> camel Case
      .split(/\B(?=[A-Z]{2,})/) // Now split the multi-uppercases customerID -> customer,ID
      .join(' ') // And join back with spaces.
      .split(' ') // Split all the spaces again, this time we're fully converted
      .join('_') // And finally snake_case things up
      .toLowerCase() // With a nice lower case
  }

Conversion examples:

'snakeCase'                  => 'snake_case'
'CustomerID'                 => 'customer_id'
'GPS'                        => 'gps'
'IP-address'                 => 'ip_address'
'Another & Another, one too' => 'another_another_one_too'
'random ----- Thing123'      => 'random_thing123'
'kebab-case-example'         => 'kebab_case_example'

Orwellophile's answer does not work for uppercase words delimited by a space: E.g: 'TEST CASE' => t_e_s_t_case

The following solution does not break consecutive upper case characters and is a little shorter:

const snakeCase = str =>
  str &&
  str
    .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
    .map(x => x.toLowerCase())
    .join('_');

However, trailing underscores after uppercase words (examples from Orwellophile as well), do not work properly. E.g: 'TheID_' => the_i_d

Taken from https://www.w3resource.com/javascript-exercises/fundamental/javascript-fundamental-exercise-120.php.

Related