use serde_json::Value;
pub fn snake_to_camel(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut capitalize_next = false;
for c in s.chars() {
if c == '_' {
capitalize_next = true;
} else if capitalize_next {
result.push(c.to_ascii_uppercase());
capitalize_next = false;
} else {
result.push(c);
}
}
if capitalize_next {
result.push('_');
}
result
}
pub fn camel_to_snake(s: &str) -> String {
let mut result = String::with_capacity(s.len() + 10);
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c.is_uppercase() {
if !result.is_empty() {
let next_is_upper_or_end = chars.peek().map(|n| n.is_uppercase()).unwrap_or(true);
let prev_was_upper = result
.chars()
.last()
.map(|p| p.is_uppercase())
.unwrap_or(false);
if !prev_was_upper || !next_is_upper_or_end {
result.push('_');
}
}
result.push(
c.to_lowercase()
.next()
.expect("to_lowercase() always yields at least one char"),
);
} else {
result.push(c);
}
}
result
}
pub fn transform_keys_to_snake(value: Value) -> Value {
match value {
Value::Object(map) => {
let mut new_map = serde_json::Map::new();
for (key, val) in map {
let new_key = camel_to_snake(&key);
let new_val = transform_keys_to_snake(val);
new_map.insert(new_key, new_val);
}
Value::Object(new_map)
}
Value::Array(arr) => Value::Array(arr.into_iter().map(transform_keys_to_snake).collect()),
other => other,
}
}
pub fn sort_json_keys(value: Value) -> Value {
match value {
Value::Object(map) => {
let mut entries: Vec<_> = map.into_iter().collect();
entries.sort_by(|(a, _), (b, _)| a.cmp(b));
let sorted: serde_json::Map<String, Value> = entries
.into_iter()
.map(|(k, v)| (k, sort_json_keys(v)))
.collect();
Value::Object(sorted)
}
Value::Array(arr) => Value::Array(arr.into_iter().map(sort_json_keys).collect()),
other => other,
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_snake_to_camel_simple() {
assert_eq!(snake_to_camel("first_name"), "firstName");
assert_eq!(snake_to_camel("email_address"), "emailAddress");
assert_eq!(snake_to_camel("single"), "single");
}
#[test]
fn test_snake_to_camel_trailing_underscore() {
assert_eq!(snake_to_camel("digital_file_s_"), "digitalFileS_");
assert_eq!(snake_to_camel("page_s_"), "pageS_");
assert_eq!(
snake_to_camel(&camel_to_snake("digitalFileS_")),
"digitalFileS_"
);
assert_eq!(snake_to_camel(&camel_to_snake("pageS_")), "pageS_");
}
#[test]
fn test_camel_to_snake_simple() {
assert_eq!(camel_to_snake("firstName"), "first_name");
assert_eq!(camel_to_snake("lastName"), "last_name");
assert_eq!(camel_to_snake("emailAddress"), "email_address");
}
#[test]
fn test_camel_to_snake_single_word() {
assert_eq!(camel_to_snake("name"), "name");
assert_eq!(camel_to_snake("id"), "id");
}
#[test]
fn test_camel_to_snake_already_snake() {
assert_eq!(camel_to_snake("first_name"), "first_name");
assert_eq!(camel_to_snake("email_address"), "email_address");
}
#[test]
fn test_camel_to_snake_acronyms() {
assert_eq!(camel_to_snake("XMLParser"), "x_m_l_parser");
assert_eq!(camel_to_snake("getHTTPResponse"), "get_h_t_t_p_response");
assert_eq!(camel_to_snake("parseJSON"), "parse_j_s_o_n");
}
#[test]
fn test_camel_to_snake_mixed() {
assert_eq!(camel_to_snake("getURLForID"), "get_u_r_l_for_i_d");
}
#[test]
fn test_transform_keys_nested() {
let input = json!({
"firstName": "John",
"lastName": "Doe",
"contactInfo": {
"emailAddress": "john@example.com",
"phoneNumber": "123-456-7890"
},
"addresses": [
{
"streetName": "Main St",
"zipCode": "12345"
}
]
});
let result = transform_keys_to_snake(input);
assert_eq!(result["first_name"], "John");
assert_eq!(result["last_name"], "Doe");
assert_eq!(result["contact_info"]["email_address"], "john@example.com");
assert_eq!(result["contact_info"]["phone_number"], "123-456-7890");
assert_eq!(result["addresses"][0]["street_name"], "Main St");
assert_eq!(result["addresses"][0]["zip_code"], "12345");
}
#[test]
fn test_transform_keys_preserves_values() {
let input = json!({
"camelKey": "camelValue",
"numberKey": 42,
"boolKey": true,
"nullKey": null
});
let result = transform_keys_to_snake(input);
assert_eq!(result["camel_key"], "camelValue"); assert_eq!(result["number_key"], 42);
assert_eq!(result["bool_key"], true);
assert!(result["null_key"].is_null());
}
}