use std::fmt::Write as _;
pub(crate) fn escape_json_string(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
'\u{08}' => out.push_str("\\b"),
'\u{0c}' => out.push_str("\\f"),
c if (c as u32) < 0x20 => {
let _ = write!(out, "\\u{:04x}", c as u32);
}
c => out.push(c),
}
}
out.push('"');
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::json::JsonValue;
#[test]
fn plain_string() {
assert_eq!(escape_json_string("hello"), r#""hello""#);
}
#[test]
fn escapes_quote_and_backslash() {
assert_eq!(escape_json_string("a\"b\\c"), r#""a\"b\\c""#);
}
#[test]
fn escapes_short_control_chars() {
assert_eq!(escape_json_string("\n\r\t"), r#""\n\r\t""#);
assert_eq!(escape_json_string("\u{08}\u{0c}"), r#""\b\f""#);
}
#[test]
fn escapes_other_control_chars_as_unicode() {
assert_eq!(escape_json_string("\u{1}\u{1f}"), "\"\\u0001\\u001f\"");
}
#[test]
fn preserves_non_ascii() {
assert_eq!(escape_json_string("Sméagol 🧙"), "\"Sméagol 🧙\"");
}
#[test]
fn output_parses_back_to_original() {
for input in ["plain", "a\"b\\c", "tab\there", "\u{1}\u{1f}", "Sméagol 🧙"] {
let json = format!("{{\"v\":{}}}", escape_json_string(input));
let parsed = JsonValue::parse(&json).unwrap();
assert_eq!(parsed.get_str("v"), Some(input));
}
}
}