windjammer-runtime 0.48.0

Runtime library for Windjammer standard library implementations
Documentation
//! JSON serialization and deserialization
//!
//! Windjammer's `std::json` module maps to these functions.

use serde_json::Value;

/// Parse JSON string into a Value
pub fn parse(s: &str) -> Result<Value, String> {
    serde_json::from_str(s).map_err(|e| e.to_string())
}

/// Convert Value to JSON string
pub fn stringify(value: &Value) -> Result<String, String> {
    serde_json::to_string(value).map_err(|e| e.to_string())
}

/// Convert Value to pretty-printed JSON string
pub fn stringify_pretty(value: &Value) -> Result<String, String> {
    serde_json::to_string_pretty(value).map_err(|e| e.to_string())
}

/// Create a JSON object (map)
pub fn object() -> Value {
    Value::Object(serde_json::Map::new())
}

/// Create a JSON array
pub fn array() -> Value {
    Value::Array(Vec::new())
}

/// Create a JSON null value
pub fn null() -> Value {
    Value::Null
}

/// Create a JSON boolean value
pub fn boolean(b: bool) -> Value {
    Value::Bool(b)
}

/// Create a JSON number value from i64
pub fn number_i64(n: i64) -> Value {
    Value::Number(n.into())
}

/// Create a JSON number value from f64
pub fn number_f64(n: f64) -> Result<Value, String> {
    serde_json::Number::from_f64(n)
        .map(Value::Number)
        .ok_or_else(|| "Invalid number".to_string())
}

/// Create a JSON string value
pub fn string(s: &str) -> Value {
    Value::String(s.to_string())
}

/// Get value from object by key
pub fn get<'a>(value: &'a Value, key: &str) -> Option<&'a Value> {
    value.get(key)
}

/// Type predicates (Windjammer `std::json` surface)
pub fn is_object(value: &Value) -> bool {
    value.is_object()
}

pub fn is_array(value: &Value) -> bool {
    value.is_array()
}

pub fn is_string(value: &Value) -> bool {
    value.is_string()
}

pub fn is_number(value: &Value) -> bool {
    value.is_number()
}

pub fn is_bool(value: &Value) -> bool {
    value.is_boolean()
}

pub fn is_null(value: &Value) -> bool {
    value.is_null()
}

/// Value coercions
pub fn as_str(value: &Value) -> Option<String> {
    value.as_str().map(|s| s.to_string())
}

pub fn as_i64(value: &Value) -> Option<i64> {
    value.as_i64()
}

pub fn as_f64(value: &Value) -> Option<f64> {
    value.as_f64()
}

pub fn as_bool(value: &Value) -> Option<bool> {
    value.as_bool()
}

/// Get string from object by key
pub fn get_string(value: &Value, key: &str) -> Option<String> {
    value
        .get(key)
        .and_then(|v| v.as_str().map(|s| s.to_string()))
}

/// Get number from object by key  
pub fn get_number(value: &Value, key: &str) -> Option<f64> {
    value.get(key).and_then(|v| v.as_f64())
}

/// Get boolean from object by key
pub fn get_bool(value: &Value, key: &str) -> Option<bool> {
    value.get(key).and_then(|v| v.as_bool())
}

/// Set value in object by key
pub fn set(value: &mut Value, key: &str, new_value: Value) -> Result<(), String> {
    if let Some(obj) = value.as_object_mut() {
        obj.insert(key.to_string(), new_value);
        Ok(())
    } else {
        Err("Value is not an object".to_string())
    }
}

/// Get length of array or object
pub fn len(value: &Value) -> usize {
    match value {
        Value::Array(arr) => arr.len(),
        Value::Object(obj) => obj.len(),
        _ => 0,
    }
}

/// Check if array or object is empty
pub fn is_empty(value: &Value) -> bool {
    len(value) == 0
}

/// Get array element by index
pub fn get_index(value: &Value, index: usize) -> Option<&Value> {
    value.get(index)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_stringify() {
        let json_str = r#"{"name":"Alice","age":30}"#;
        let value = parse(json_str).unwrap();
        let result = stringify(&value).unwrap();

        // Parse both to compare (order might differ)
        let original: serde_json::Value = serde_json::from_str(json_str).unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
        assert_eq!(original, parsed);
    }

    #[test]
    fn test_constructors() {
        assert_eq!(null(), Value::Null);
        assert_eq!(boolean(true), Value::Bool(true));
        assert_eq!(string("test"), Value::String("test".to_string()));
        assert!(matches!(number_i64(42), Value::Number(_)));
    }

    #[test]
    fn test_pretty_print() {
        let value = parse(r#"{"a":1,"b":2}"#).unwrap();
        let pretty = stringify_pretty(&value).unwrap();
        assert!(pretty.contains('\n'));
        assert!(pretty.contains("  "));
    }

    #[test]
    fn test_get() {
        let json = r#"{"name": "Alice", "age": 30, "active": true}"#;
        let value = parse(json).unwrap();

        assert!(get(&value, "name").is_some());
        assert_eq!(get_string(&value, "name"), Some("Alice".to_string()));
        assert_eq!(get_number(&value, "age"), Some(30.0));
        assert_eq!(get_bool(&value, "active"), Some(true));
    }

    #[test]
    fn test_set() {
        let json = r#"{"name": "Alice"}"#;
        let mut value = parse(json).unwrap();

        let new_name = string("Bob");
        let result = set(&mut value, "name", new_name);
        assert!(result.is_ok());
        assert_eq!(get_string(&value, "name"), Some("Bob".to_string()));
    }

    #[test]
    fn test_len_is_empty() {
        let json_array = r#"[1, 2, 3, 4, 5]"#;
        let value_array = parse(json_array).unwrap();
        assert_eq!(len(&value_array), 5);
        assert!(!is_empty(&value_array));

        let json_object = r#"{"a": 1, "b": 2}"#;
        let value_object = parse(json_object).unwrap();
        assert_eq!(len(&value_object), 2);
        assert!(!is_empty(&value_object));

        let empty_array = array();
        assert_eq!(len(&empty_array), 0);
        assert!(is_empty(&empty_array));
    }

    #[test]
    fn test_type_predicates_and_coercions() {
        let value = parse(r#"{"name":"Alice","age":30,"active":true,"nil":null}"#).unwrap();
        assert!(is_object(&value));
        assert!(!is_array(&value));

        let name = get(&value, "name").unwrap();
        assert!(is_string(name));
        assert_eq!(as_str(name), Some("Alice".to_string()));

        let age = get(&value, "age").unwrap();
        assert!(is_number(age));
        assert_eq!(as_i64(age), Some(30));

        let active = get(&value, "active").unwrap();
        assert!(is_bool(active));
        assert_eq!(as_bool(active), Some(true));

        let nil = get(&value, "nil").unwrap();
        assert!(is_null(nil));
    }
}