Skip to content

Instantly share code, notes, and snippets.

@dmsl1805
Forked from ivanbruel/SnakeCase.swift
Last active September 3, 2023 16:11
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save dmsl1805/ad9a14b127d0409cf9621dc13d237457 to your computer and use it in GitHub Desktop.
Save dmsl1805/ad9a14b127d0409cf9621dc13d237457 to your computer and use it in GitHub Desktop.
Camel case to snake case in Swift
extension String {
func snakeCased() -> String? {
let pattern = "([a-z0-9])([A-Z])"
let regex = try? NSRegularExpression(pattern: pattern, options: [])
let range = NSRange(location: 0, length: count)
return regex?.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: "$1_$2").lowercased()
}
}
@jangelsb
Copy link

Tweaked this a little bit to handle acronyms and single letter words:

extension String {
    func camelCaseToSnakeCase() -> String {
        let acronymPattern = "([A-Z]+)([A-Z][a-z]|[0-9])"
        let normalPattern = "([a-z0-9])([A-Z])"
        return self.processCamalCaseRegex(pattern: acronymPattern)?
            .processCamalCaseRegex(pattern: normalPattern)?.lowercased() ?? self.lowercased()
    }
    
    fileprivate func processCamalCaseRegex(pattern: String) -> String? {
        let regex = try? NSRegularExpression(pattern: pattern, options: [])
        let range = NSRange(location: 0, length: count)
        return regex?.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: "$1_$2")
    }
}

input:

print("JSON123".camelCaseToSnakeCase())
print("JSON123Test".camelCaseToSnakeCase())
print("ThisIsAnAITest".camelCaseToSnakeCase())
print("ThisIsATest".camelCaseToSnakeCase())
print("1234ThisIsATest".camelCaseToSnakeCase())

output:

json_123
json_123_test
this_is_an_ai_test
this_is_a_test
1234_this_is_a_test

@ben-p-commits
Copy link

This is an awesome string extension, good stuff. Using for consistency in my analytics

@heestand-xyz
Copy link

Nice fix for acronyms!

@gonzaga
Copy link

gonzaga commented Mar 19, 2021

One more case to consider:

print("this1234".camelCaseToSnakeCase())

will output
this1234

Fix:

extension String {
  func camelCaseToSnakeCase() -> String {
    let acronymPattern = "([A-Z]+)([A-Z][a-z]|[0-9])"
    let fullWordsPattern = "([a-z])([A-Z]|[0-9])"
    let digitsFirstPattern = "([0-9])([A-Z])"
    return self.processCamelCaseRegex(pattern: acronymPattern)?
      .processCamelCaseRegex(pattern: fullWordsPattern)?
      .processCamelCaseRegex(pattern:digitsFirstPattern)?.lowercased() ?? self.lowercased()
  }

  fileprivate func processCamelCaseRegex(pattern: String) -> String? {
    let regex = try? NSRegularExpression(pattern: pattern, options: [])
    let range = NSRange(location: 0, length: count)
    return regex?.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: "$1_$2")
  }
}

@superarts
Copy link

If you don't want to pollute the Foundation namespace by introducing a String extension (which is fine if you are writing an app, but not encouraged if you are writing a library), here's the function version that can be placed in a string utility:

    private func flattenCamalCase(string: String, separator: String = " ") -> String {
        let acronymPattern = "([A-Z]+)([A-Z][a-z]|[0-9])"
        let normalPattern = "([a-z0-9])([A-Z])"
        if
            let processedString = processCamalCaseRegex(string: string, pattern: acronymPattern, separator: separator),
            let finalString = processCamalCaseRegex(string: processedString, pattern: normalPattern, separator: separator)?.lowercased()
        {
            return finalString
        } else {
            return string.lowercased()
        }
    }

    private func processCamalCaseRegex(string: String, pattern: String, separator: String = " ") -> String? {
        let regex = try? NSRegularExpression(pattern: pattern, options: [])
        let range = NSRange(location: 0, length: string.count)
        return regex?.stringByReplacingMatches(in: string, options: [], range: range, withTemplate: "$1\(separator)$2")
    }

@yoni-placer
Copy link

@superarts Why? if your code is private, even extensions can't be seen or used, as far as I checked.

@superarts
Copy link

@yoni-placer this is totally subjective. It's all up to what coding standard you set for yourself. If I'm writing a hobby project, I'd totally go with whatever String extension to make my life easier. However, if I'm working on a project with reusability in mind, I'd consider some scenarios and evaluate the technical approach.

Let's say I'm writing a shared framework that's used across multiple projects. I want flattenCamalCase function implemented somewhere in my framework. If I would put it in a extension String, what if one of my downstream projects uses another framework which also has a function called flattenCamalCase in its own string extension, and both implementation are producing different results, and in one of the files of my downstream project, both frameworks need to be imported?

It sounds like I'm describing an impossible use case, but to be honest, if every framework was trying to put these "helper" functions inside string extensions, you would see a lot of namespace conflicts. The solution is quite simple though: don't do this in a reusable components. Instead, put it under MyFramework.StringUtility.flattenCamalCase as I described, so it's under a specific namespace.

Another problem is dependency management. I used to be a big fan of system class extension myself, as I just needed the write the minimum lines of code. But now when I think about it, I can't justify the fact that just to save a function parameter (self in extension, first parameter in a utility class/struct/enum), I need to make flattenCamalCase global. When you said private, I assume you didn't mean the technical term of private, as your string extension is very likely to be internal. And by doing that, you care making the whole function flattenCamalCase global across your whole project, which is as bad as using global functions and variables. Again, if it's a hobby project I wouldn't pay too much attention about dependency injection, but for a commercial project, I can't imagine a 5 or 10 developers team manage a codebase without some sort of DI.

I'm writing all these not just to answer your question. In the end, it's personal preference and is totally subjective. But if you really care about SOLID principles, which by the way are pretty "solid", this kind of discussions are quite helpful to clear up our minds. So always agree to disagree :)

@aehlke
Copy link

aehlke commented Aug 22, 2023

just use fileprivate extension

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