Skip to main content

elph_ai/utils/
json_parse.rs

1use serde_json::Value;
2
3const VALID_JSON_ESCAPES: &[char] = &['"', '\\', '/', 'b', 'f', 'n', 'r', 't', 'u'];
4
5fn is_control_character(ch: char) -> bool {
6    let code = ch as u32;
7    code <= 0x1f
8}
9
10fn escape_control_character(ch: char) -> String {
11    match ch {
12        '\x08' => "\\b".to_string(),
13        '\x0C' => "\\f".to_string(),
14        '\n' => "\\n".to_string(),
15        '\r' => "\\r".to_string(),
16        '\t' => "\\t".to_string(),
17        _ => format!("\\u{:04x}", ch as u32),
18    }
19}
20
21/// Repairs malformed JSON string literals by escaping control characters.
22pub fn repair_json(json: &str) -> String {
23    let mut repaired = String::new();
24    let mut in_string = false;
25    let mut chars = json.chars().peekable();
26
27    while let Some(ch) = chars.next() {
28        if !in_string {
29            repaired.push(ch);
30            if ch == '"' {
31                in_string = true;
32            }
33            continue;
34        }
35
36        if ch == '"' {
37            repaired.push(ch);
38            in_string = false;
39            continue;
40        }
41
42        if ch == '\\' {
43            if let Some(&next) = chars.peek() {
44                if next == 'u' {
45                    let unicode: String = chars.by_ref().take(5).collect();
46                    if unicode.len() == 5 && unicode[1..].chars().all(|c| c.is_ascii_hexdigit()) {
47                        repaired.push_str(&unicode);
48                        continue;
49                    }
50                }
51                if VALID_JSON_ESCAPES.contains(&next) {
52                    repaired.push(ch);
53                    repaired.push(chars.next().unwrap());
54                    continue;
55                }
56            }
57            repaired.push_str("\\\\");
58            continue;
59        }
60
61        if is_control_character(ch) {
62            repaired.push_str(&escape_control_character(ch));
63        } else {
64            repaired.push(ch);
65        }
66    }
67
68    repaired
69}
70
71pub fn parse_json_with_repair(json: &str) -> Result<Value, serde_json::Error> {
72    match serde_json::from_str::<Value>(json) {
73        Ok(v) => Ok(v),
74        Err(e) => {
75            let repaired = repair_json(json);
76            if repaired != json {
77                serde_json::from_str(&repaired)
78            } else {
79                Err(e)
80            }
81        }
82    }
83}
84
85/// Parse potentially incomplete JSON during streaming.
86pub fn parse_streaming_json(partial_json: Option<&str>) -> Value {
87    let partial_json = partial_json.unwrap_or("").trim();
88    if partial_json.is_empty() {
89        return Value::Object(serde_json::Map::new());
90    }
91
92    if let Ok(v) = parse_json_with_repair(partial_json) {
93        return v;
94    }
95
96    // Best-effort partial parse: close open braces/brackets
97    for suffix in ["", "}", "]}", "\"}", "\":\"\"}", "]}"] {
98        let attempt = format!("{partial_json}{suffix}");
99        if let Ok(v) = parse_json_with_repair(&attempt) {
100            return v;
101        }
102    }
103
104    Value::Object(serde_json::Map::new())
105}