Skip to content

Instantly share code, notes, and snippets.

@sidharthkuruvila
Created July 21, 2012 06:30
Show Gist options
  • Select an option

  • Save sidharthkuruvila/3154845 to your computer and use it in GitHub Desktop.

Select an option

Save sidharthkuruvila/3154845 to your computer and use it in GitHub Desktop.
Utility to functions to convert between camel case and underscore separated names
/**
* Takes a camel cased identifier name and returns an underscore separated
* name
*
* Example:
* camelToUnderscores("thisIsA1Test") == "this_is_a_1_test"
*/
def camelToUnderscores(name: String) = "[A-Z\\d]".r.replaceAllIn(name, {m =>
"_" + m.group(0).toLowerCase()
})
/*
* Takes an underscore separated identifier name and returns a camel cased one
*
* Example:
* underscoreToCamel("this_is_a_1_test") == "thisIsA1Test"
*/
def underscoreToCamel(name: String) = "_([a-z\\d])".r.replaceAllIn(name, {m =>
m.group(1).toUpperCase()
})
@acmitch

acmitch commented Jun 23, 2018

Copy link
Copy Markdown

@dmateusp 👍 exactly what I needed! Would be great if worked with numeric text:

actual: DirectlyEmployedOr1099Resources => directly_employed_or1099resources
expected: DirectlyEmployedOr1099Resources => directly_employed_or_1099_resources

@banshee

banshee commented Jun 24, 2018

Copy link
Copy Markdown

@dmateusp FYI, that causes a problem with things like camelToSnake("IsAString") => is_astring, when it should be is_a_string.

@ruloweb

ruloweb commented Nov 1, 2018

Copy link
Copy Markdown

I prefer not to use regex, this approach runs in n time, it's tail recursive and does not convert consecutive upper chars, e.g. ThisIsCAmel -> this_is_camel:

def camel2Underscore(s: String): String = {
  @tailrec def camel2Underscore(s: String, output: String, lastUppercase: Boolean): String =
    if (s.isEmpty) output
    else {
      val c = if (s.head.isUpper && !lastUppercase) "_" + s.head.toLower else s.head.toLower
      camel2Underscore(s.tail, output + c, s.head.isUpper && !lastUppercase)
    }

  camel2Underscore(s, "", true)
}

@amackillop

amackillop commented Apr 10, 2019

Copy link
Copy Markdown

@ruloweb This is nice but won't work if the two consecutive uppercase chars are at the start (ie. THisIsCamel) or if there are greater than two consecutive uppercase characters elsewhere (ie. ThisIsCAMel). Consequently, this will fail for converting all caps as well.

I adjusted the code to support the all caps case:

 def camel2Snake(str: String): String = {
    @tailrec
    def camel2SnakeRec(s: String, output: String, lastUppercase: Boolean): String =
      if (s.isEmpty) output
      else {
        val c = if (s.head.isUpper && !lastUppercase) "_" + s.head.toLower else s.head.toLower
        camel2SnakeRec(s.tail, output + c, s.head.isUpper && !lastUppercase)
      }
    if (str.forall(_.isUpper)) str.map(_.toLower)
    else {
      camel2SnakeRec(str, "", true)
    }
  }

@0dilon

0dilon commented Dec 19, 2019

Copy link
Copy Markdown

@amackillop, it doesnt't work for "THISIsADog" (it gives "t_hi_sis_adog")

i suggest the following code :

def camel2Snake(str: String): String = {

  val headInUpperCase = str.takeWhile(c => c.isUpper || c.isDigit)
  val tailAfterHeadInUppercase = str.dropWhile(c => c.isUpper || c.isDigit)

  if (tailAfterHeadInUppercase.isEmpty) headInUpperCase.toLowerCase else {
    val firstWord = if (!headInUpperCase.dropRight(1).isEmpty) {
      headInUpperCase.last match {
        case c: Any if (c.isDigit) => headInUpperCase
        case _ => headInUpperCase.dropRight(1).toLowerCase
      }
    } else {
      headInUpperCase.toLowerCase + tailAfterHeadInUppercase.takeWhile(c => c.isLower)
    }

    if (firstWord == str.toLowerCase) {
      firstWord
    } else {
      s"${firstWord}_${camel2Snake(str.drop(firstWord.length))}"
    }

  }
}

@nicusX

nicusX commented Jan 2, 2020

Copy link
Copy Markdown

@0dilon you version stack-overflows if the camelCase already contains an underscore.
e.g. camel2Snake("foo_BarBaz")

@jnewman

jnewman commented May 18, 2021

Copy link
Copy Markdown

Late to the party, but iterate w/ Chars seems fine too if you're ok w/ no handling -s in the Strings

val camelToKebab: String => String = _.foldLeft("") {
      case (acc, chr) if chr.isUpper => acc :+ '-' :+ chr.toLower
      case (acc, chr)                => acc :+ chr
    }

@Kalyan-D

Copy link
Copy Markdown

@0dilon you version stack-overflows if the camelCase already contains an underscore.

any update on this @0dilon

@umbarger

umbarger commented Sep 21, 2021

Copy link
Copy Markdown
val camelToSnake : String => String = _.foldLeft( "" ){ (acc, c) =>
    ( c.isUpper, acc.isEmpty, acc.takeRight(1) == "_" ) match {
      case (true, false, false) => acc + "_" + c.toLower
      case (true, _, _) => acc + c.toLower
      case (false, _, _) => acc + c
    }
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment