Convert Between camelCase and snake_case
Split identifiers on separators and case boundaries, then rebuild them as snake_case or camelCase without external crates.
Code
fn split_words(input: &str) -> Vec<String> {
let mut words = Vec::new();
let mut current = String::new();
let characters: Vec<char> = input.chars().collect();
for (index, &character) in characters.iter().enumerate() {
if character == '_' || character == '-' || character == ' ' {
if !current.is_empty() {
words.push(std::mem::take(&mut current));
}
continue;
}
let previous = if index > 0 { Some(characters[index - 1]) } else { None };
let next = characters.get(index + 1).copied();
let boundary = (character.is_uppercase()
&& previous.map_or(false, |value| value.is_lowercase() || value.is_ascii_digit()))
|| (character.is_uppercase()
&& previous.map_or(false, |value| value.is_uppercase())
&& next.map_or(false, |value| value.is_lowercase()));
if boundary && !current.is_empty() {
words.push(std::mem::take(&mut current));
}
current.push(character.to_ascii_lowercase());
}
if !current.is_empty() {
words.push(current);
}
words
}
fn to_snake_case(input: &str) -> String {
split_words(input).join("_")
}
fn to_camel_case(input: &str) -> String {
let mut words = split_words(input);
if words.is_empty() {
return String::new();
}
let mut output = words.remove(0);
for word in words {
let mut characters = word.chars();
if let Some(first) = characters.next() {
output.push(first.to_ascii_uppercase());
output.extend(characters);
}
}
output
}
fn main() {
for sample in ["HTTPServer", "user_profile_id", "OrderServiceConfig", "v2Beta"] {
println!("{sample} -> {} / {}", to_snake_case(sample), to_camel_case(sample));
}
assert_eq!(to_snake_case("HTTPServer"), "http_server");
assert_eq!(to_snake_case("user_profile_id"), "user_profile_id");
assert_eq!(to_camel_case("order-service-config"), "orderServiceConfig");
assert_eq!(to_camel_case("already_snake"), "alreadySnake");
}
Report a problem with this exampleOpens a pre-filled issue on GitHub.