Skip to main content

sage_runtime/stdlib/
json.rs

1//! JSON helper functions for the Sage standard library.
2
3use serde_json::Value;
4
5/// Parse a JSON string, validating that it's well-formed JSON.
6/// Returns the original string if valid.
7pub fn json_parse(s: &str) -> Result<String, String> {
8    let _: Value = serde_json::from_str(s).map_err(|e| format!("invalid JSON: {}", e))?;
9    Ok(s.to_string())
10}
11
12/// Get a field from a JSON object as a string.
13/// Returns None if the field doesn't exist or isn't a string.
14#[must_use]
15pub fn json_get(json: &str, field: &str) -> Option<String> {
16    let value: Value = serde_json::from_str(json).ok()?;
17    match value.get(field)? {
18        Value::String(s) => Some(s.clone()),
19        other => Some(other.to_string()),
20    }
21}
22
23/// Get a field from a JSON object as an integer.
24/// Returns None if the field doesn't exist or isn't a number.
25#[must_use]
26pub fn json_get_int(json: &str, field: &str) -> Option<i64> {
27    let value: Value = serde_json::from_str(json).ok()?;
28    value.get(field)?.as_i64()
29}
30
31/// Get a field from a JSON object as a float.
32/// Returns None if the field doesn't exist or isn't a number.
33#[must_use]
34pub fn json_get_float(json: &str, field: &str) -> Option<f64> {
35    let value: Value = serde_json::from_str(json).ok()?;
36    value.get(field)?.as_f64()
37}
38
39/// Get a field from a JSON object as a boolean.
40/// Returns None if the field doesn't exist or isn't a boolean.
41#[must_use]
42pub fn json_get_bool(json: &str, field: &str) -> Option<bool> {
43    let value: Value = serde_json::from_str(json).ok()?;
44    value.get(field)?.as_bool()
45}
46
47/// Get a field from a JSON object as a list of strings.
48/// Each array element is converted to its JSON string representation.
49/// Returns None if the field doesn't exist or isn't an array.
50#[must_use]
51pub fn json_get_list(json: &str, field: &str) -> Option<Vec<String>> {
52    let value: Value = serde_json::from_str(json).ok()?;
53    let arr = value.get(field)?.as_array()?;
54    Some(
55        arr.iter()
56            .map(|v| match v {
57                Value::String(s) => s.clone(),
58                other => other.to_string(),
59            })
60            .collect(),
61    )
62}
63
64/// Convert a value to a JSON string.
65/// Works best with strings, but accepts any type via its Debug representation.
66#[must_use]
67pub fn json_stringify_string(s: &str) -> String {
68    serde_json::to_string(s).unwrap_or_else(|_| format!("\"{}\"", s))
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_json_parse() {
77        assert!(json_parse(r#"{"name": "Alice"}"#).is_ok());
78        assert!(json_parse(r#"[1, 2, 3]"#).is_ok());
79        assert!(json_parse(r#"invalid json"#).is_err());
80    }
81
82    #[test]
83    fn test_json_get() {
84        let json = r#"{"name": "Alice", "age": 30}"#;
85        assert_eq!(json_get(json, "name"), Some("Alice".to_string()));
86        assert_eq!(json_get(json, "age"), Some("30".to_string()));
87        assert_eq!(json_get(json, "missing"), None);
88    }
89
90    #[test]
91    fn test_json_get_int() {
92        let json = r#"{"count": 42, "name": "test"}"#;
93        assert_eq!(json_get_int(json, "count"), Some(42));
94        assert_eq!(json_get_int(json, "name"), None);
95        assert_eq!(json_get_int(json, "missing"), None);
96    }
97
98    #[test]
99    fn test_json_get_float() {
100        let json = r#"{"value": 3.14, "count": 42}"#;
101        assert!((json_get_float(json, "value").unwrap() - 3.14).abs() < 0.001);
102        assert_eq!(json_get_float(json, "count"), Some(42.0));
103    }
104
105    #[test]
106    fn test_json_get_bool() {
107        let json = r#"{"active": true, "deleted": false}"#;
108        assert_eq!(json_get_bool(json, "active"), Some(true));
109        assert_eq!(json_get_bool(json, "deleted"), Some(false));
110        assert_eq!(json_get_bool(json, "missing"), None);
111    }
112
113    #[test]
114    fn test_json_get_list() {
115        let json = r#"{"items": ["a", "b", "c"], "numbers": [1, 2, 3]}"#;
116        assert_eq!(
117            json_get_list(json, "items"),
118            Some(vec!["a".to_string(), "b".to_string(), "c".to_string()])
119        );
120        assert_eq!(
121            json_get_list(json, "numbers"),
122            Some(vec!["1".to_string(), "2".to_string(), "3".to_string()])
123        );
124        assert_eq!(json_get_list(json, "missing"), None);
125    }
126
127    #[test]
128    fn test_json_stringify_string() {
129        assert_eq!(json_stringify_string("hello"), r#""hello""#);
130        assert_eq!(json_stringify_string("hello\nworld"), r#""hello\nworld""#);
131    }
132}