Skip to main content

aether_core/
toon.rs

1use serde_json::{Value, Map};
2
3/// Token-Oriented Object Notation (TOON) Serializer.
4/// Reduces token usage by 30-60% compared to JSON.
5pub struct Toon;
6
7impl Toon {
8    /// Serialize a JSON value to TOON format.
9    pub fn serialize(value: &Value) -> String {
10        match value {
11            Value::Object(map) => Self::serialize_object(map, 0),
12            Value::Array(arr) => Self::serialize_array(arr, 0),
13            Value::String(s) => s.clone(),
14            Value::Number(n) => n.to_string(),
15            Value::Bool(b) => b.to_string(),
16            Value::Null => "~".to_string(),
17        }
18    }
19
20    fn serialize_object(map: &Map<String, Value>, indent: usize) -> String {
21        let mut out = String::new();
22        let pad = "  ".repeat(indent);
23        
24        for (k, v) in map {
25            match v {
26                Value::Object(child_map) => {
27                    out.push_str(&format!("{}{}:\n{}", pad, k, Self::serialize_object(child_map, indent + 1)));
28                }
29                Value::Array(arr) => {
30                    out.push_str(&format!("{}{}[{}]:\n{}", pad, k, arr.len(), Self::serialize_array(arr, indent + 1)));
31                }
32                _ => {
33                    out.push_str(&format!("{}{}: {}\n", pad, k, Self::serialize(v)));
34                }
35            }
36        }
37        out
38    }
39
40    fn serialize_array(arr: &[Value], indent: usize) -> String {
41        if arr.is_empty() {
42            return "[]".to_string();
43        }
44
45        // Check if it's a homogeneous list of objects to use tabular TOON format
46        if let Some(first) = arr.first() {
47            if let Value::Object(first_map) = first {
48                let keys: Vec<String> = first_map.keys().cloned().collect();
49                let mut out = format!("{{{}}}:\n", keys.join(","));
50                let pad = "  ".repeat(indent);
51
52                for item in arr {
53                    if let Value::Object(item_map) = item {
54                        let values: Vec<String> = keys.iter()
55                            .map(|k| item_map.get(k).map(|v| Self::serialize_flat(v)).unwrap_or_else(|| "~".to_string()))
56                            .collect();
57                        out.push_str(&format!("{}{}\n", pad, values.join(",")));
58                    }
59                }
60                return out;
61            }
62        }
63
64        // Fallback for simple arrays
65        let mut out = String::new();
66        let pad = "  ".repeat(indent);
67        for v in arr {
68            out.push_str(&format!("{}- {}\n", pad, Self::serialize(v).trim()));
69        }
70        out
71    }
72
73    fn serialize_flat(value: &Value) -> String {
74        match value {
75            Value::String(s) => s.replace(',', "\\,").to_string(),
76            Value::Number(n) => n.to_string(),
77            Value::Bool(b) => if *b { "T" } else { "F" }.to_string(),
78            _ => ".".to_string(),
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use serde_json::json;
87
88    #[test]
89    fn test_toon_tabular() {
90        let data = json!([
91            {"id": 1, "name": "Apple", "price": 10},
92            {"id": 2, "name": "Banana", "price": 5}
93        ]);
94        let toon = Toon::serialize(&data);
95        assert!(toon.contains("{id,name,price}"));
96        assert!(toon.contains("1,Apple,10"));
97    }
98
99    #[test]
100    fn test_toon_object() {
101        let data = json!({
102            "user": "admin",
103            "meta": { "last_login": "2024-01-01" }
104        });
105        let toon = Toon::serialize(&data);
106        assert!(toon.contains("user: admin"));
107        assert!(toon.contains("meta:"));
108    }
109}