Skip to main content

comnum_sim/
json.rs

1//! A minimal JSON writer for the reports (no dependency).
2
3use std::fmt::Write;
4
5/// A JSON string literal.
6pub fn string(s: &str) -> String {
7    let mut out = String::with_capacity(s.len() + 2);
8    out.push('"');
9    for c in s.chars() {
10        match c {
11            '"' => out.push_str("\\\""),
12            '\\' => out.push_str("\\\\"),
13            '\n' => out.push_str("\\n"),
14            '\r' => out.push_str("\\r"),
15            '\t' => out.push_str("\\t"),
16            c if (c as u32) < 0x20 => {
17                let _ = write!(out, "\\u{:04x}", c as u32);
18            }
19            c => out.push(c),
20        }
21    }
22    out.push('"');
23    out
24}
25
26/// A JSON number (`null` for a non-finite value).
27pub fn number(v: f64) -> String {
28    if v.is_finite() { format!("{v}") } else { "null".to_string() }
29}
30
31/// A JSON object from already-encoded values.
32pub fn object(fields: &[(&str, String)]) -> String {
33    let body: Vec<String> = fields.iter().map(|(k, v)| format!("{}: {}", string(k), v)).collect();
34    format!("{{{}}}", body.join(", "))
35}
36
37/// A JSON array from already-encoded values.
38pub fn array(items: &[String]) -> String {
39    format!("[{}]", items.join(", "))
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn escaping() {
48        assert_eq!(string("a\"b\\c\n"), "\"a\\\"b\\\\c\\n\"");
49        assert_eq!(number(f64::NAN), "null");
50        assert_eq!(object(&[("x", number(1.5))]), "{\"x\": 1.5}");
51    }
52}