toon-format-rs 0.1.0

Token-Oriented Object Notation (TOON) parser and serializer for Rust
Documentation
use crate::error::{Error, Result};
use crate::parser::parse;
use crate::serializer::to_string;
use crate::value::Value;
use indexmap::IndexMap;
use serde_json;

/// Converts a JSON string to TOON format
pub fn json_to_toon(json_str: &str) -> Result<String> {
    let json_value: serde_json::Value =
        serde_json::from_str(json_str).map_err(|e| Error::Message(e.to_string()))?;
    let toon_value = json_value_to_toon(&json_value);
    to_string(&toon_value)
}

/// Converts a TOON string to JSON format
pub fn toon_to_json(toon_str: &str) -> Result<String> {
    let toon_value = parse(toon_str)?;
    let json_value = toon_value_to_json(&toon_value);
    serde_json::to_string(&json_value).map_err(|e| Error::Message(e.to_string()))
}

/// Converts a TOON string to pretty-printed JSON
pub fn toon_to_json_pretty(toon_str: &str) -> Result<String> {
    let toon_value = parse(toon_str)?;
    let json_value = toon_value_to_json(&toon_value);
    serde_json::to_string_pretty(&json_value).map_err(|e| Error::Message(e.to_string()))
}

/// Converts serde_json::Value to toon_format::Value
fn json_value_to_toon(json: &serde_json::Value) -> Value {
    match json {
        serde_json::Value::Null => Value::Null,
        serde_json::Value::Bool(b) => Value::Bool(*b),
        serde_json::Value::Number(n) => {
            if let Some(i) = n.as_i64() {
                Value::integer(i)
            } else if let Some(f) = n.as_f64() {
                Value::float(f)
            } else {
                Value::Number(n.to_string())
            }
        }
        serde_json::Value::String(s) => Value::String(s.clone()),
        serde_json::Value::Array(arr) => {
            Value::Array(arr.iter().map(json_value_to_toon).collect())
        }
        serde_json::Value::Object(obj) => {
            let mut map = IndexMap::new();
            for (k, v) in obj {
                map.insert(k.clone(), json_value_to_toon(v));
            }
            Value::Object(map)
        }
    }
}

/// Converts toon_format::Value to serde_json::Value
fn toon_value_to_json(toon: &Value) -> serde_json::Value {
    match toon {
        Value::Null => serde_json::Value::Null,
        Value::Bool(b) => serde_json::Value::Bool(*b),
        Value::Number(n) => {
            if let Ok(i) = n.parse::<i64>() {
                serde_json::Value::Number(i.into())
            } else if let Ok(f) = n.parse::<f64>() {
                serde_json::Number::from_f64(f)
                    .map(serde_json::Value::Number)
                    .unwrap_or_else(|| serde_json::Value::String(n.clone()))
            } else {
                serde_json::Value::String(n.clone())
            }
        }
        Value::String(s) => serde_json::Value::String(s.clone()),
        Value::Array(arr) => {
            serde_json::Value::Array(arr.iter().map(toon_value_to_json).collect())
        }
        Value::Object(obj) => {
            let mut map = serde_json::Map::new();
            for (k, v) in obj {
                map.insert(k.clone(), toon_value_to_json(v));
            }
            serde_json::Value::Object(map)
        }
    }
}

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

    #[test]
    fn test_json_to_toon() {
        let json = r#"{"id":123,"name":"Alice","active":true}"#;
        let toon = json_to_toon(json).unwrap();

        assert!(toon.contains("id: 123"));
        assert!(toon.contains("name: Alice"));
        assert!(toon.contains("active: true"));
    }

    #[test]
    fn test_toon_to_json() {
        let toon = r#"id: 123
name: Alice
active: true"#;

        let json = toon_to_json(toon).unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();

        assert_eq!(parsed["id"], 123);
        assert_eq!(parsed["name"], "Alice");
        assert_eq!(parsed["active"], true);
    }

    #[test]
    fn test_roundtrip() {
        let original = r#"{"users":[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]}"#;
        let toon = json_to_toon(original).unwrap();
        let json = toon_to_json(&toon).unwrap();

        let orig_value: serde_json::Value = serde_json::from_str(original).unwrap();
        let roundtrip_value: serde_json::Value = serde_json::from_str(&json).unwrap();

        assert_eq!(orig_value, roundtrip_value);
    }
}