Skip to main content

alopex_cli/output/
json.rs

1//! JSON array formatter
2//!
3//! Outputs data as a JSON array. Supports streaming output.
4
5use std::io::Write;
6
7use crate::error::Result;
8use crate::models::{Column, Row, Value};
9
10use super::formatter::Formatter;
11
12/// JSON array formatter.
13///
14/// Outputs data as a JSON array of objects.
15/// Supports streaming by writing array elements incrementally.
16pub struct JsonFormatter {
17    /// Column names (set on write_header)
18    columns: Vec<String>,
19    /// Whether the first row has been written
20    first_row: bool,
21}
22
23impl JsonFormatter {
24    /// Create a new JSON formatter.
25    pub fn new() -> Self {
26        Self {
27            columns: Vec::new(),
28            first_row: true,
29        }
30    }
31}
32
33impl Default for JsonFormatter {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl Formatter for JsonFormatter {
40    fn write_header(&mut self, writer: &mut dyn Write, columns: &[Column]) -> Result<()> {
41        // Store column names and start the JSON array
42        self.columns = columns.iter().map(|c| c.name.clone()).collect();
43        self.first_row = true;
44        writeln!(writer, "[")?;
45        Ok(())
46    }
47
48    fn write_row(&mut self, writer: &mut dyn Write, row: &Row) -> Result<()> {
49        // Build a JSON object from column names and values
50        let mut obj = serde_json::Map::new();
51
52        for (i, value) in row.columns.iter().enumerate() {
53            let key = self
54                .columns
55                .get(i)
56                .cloned()
57                .unwrap_or_else(|| format!("col{}", i));
58            let json_value = value_to_json(value);
59            obj.insert(key, json_value);
60        }
61
62        let json = serde_json::Value::Object(obj);
63        let json_str = serde_json::to_string_pretty(&json)?;
64
65        // Add comma before all rows except the first
66        if self.first_row {
67            self.first_row = false;
68        } else {
69            write!(writer, ",")?;
70            writeln!(writer)?;
71        }
72
73        // Indent the JSON object
74        for line in json_str.lines() {
75            writeln!(writer, "  {}", line)?;
76        }
77
78        Ok(())
79    }
80
81    fn write_footer(&mut self, writer: &mut dyn Write) -> Result<()> {
82        // Close the JSON array
83        if !self.first_row {
84            writeln!(writer)?;
85        }
86        writeln!(writer, "]")?;
87        Ok(())
88    }
89
90    fn supports_streaming(&self) -> bool {
91        true
92    }
93}
94
95/// Convert a Value to a serde_json::Value.
96fn value_to_json(value: &Value) -> serde_json::Value {
97    match value {
98        Value::Null => serde_json::Value::Null,
99        Value::Bool(b) => serde_json::Value::Bool(*b),
100        Value::Int(i) => serde_json::Value::Number((*i).into()),
101        Value::Float(f) => serde_json::Number::from_f64(*f)
102            .map(serde_json::Value::Number)
103            .unwrap_or(serde_json::Value::Null),
104        Value::Text(s) => serde_json::Value::String(s.clone()),
105        Value::Bytes(b) => {
106            // Encode bytes as array of numbers
107            serde_json::Value::Array(
108                b.iter()
109                    .map(|&byte| serde_json::Value::Number(byte.into()))
110                    .collect(),
111            )
112        }
113        Value::Vector(v) => serde_json::Value::Array(
114            v.iter()
115                .filter_map(|&f| serde_json::Number::from_f64(f as f64))
116                .map(serde_json::Value::Number)
117                .collect(),
118        ),
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use crate::models::DataType;
126
127    fn test_columns() -> Vec<Column> {
128        vec![
129            Column::new("id", DataType::Int),
130            Column::new("name", DataType::Text),
131        ]
132    }
133
134    #[test]
135    fn test_json_empty() {
136        let mut formatter = JsonFormatter::new();
137        let mut output = Vec::new();
138
139        let columns = test_columns();
140        formatter.write_header(&mut output, &columns).unwrap();
141        formatter.write_footer(&mut output).unwrap();
142
143        let result = String::from_utf8(output).unwrap();
144        assert!(result.contains('['));
145        assert!(result.contains(']'));
146    }
147
148    #[test]
149    fn test_json_single_row() {
150        let mut formatter = JsonFormatter::new();
151        let mut output = Vec::new();
152
153        let columns = test_columns();
154        formatter.write_header(&mut output, &columns).unwrap();
155
156        let row = Row::new(vec![Value::Int(1), Value::Text("Alice".to_string())]);
157        formatter.write_row(&mut output, &row).unwrap();
158
159        formatter.write_footer(&mut output).unwrap();
160
161        let result = String::from_utf8(output).unwrap();
162        assert!(result.contains("\"id\": 1"));
163        assert!(result.contains("\"name\": \"Alice\""));
164
165        // Verify it's valid JSON
166        let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
167        assert!(parsed.is_array());
168        assert_eq!(parsed.as_array().unwrap().len(), 1);
169    }
170
171    #[test]
172    fn test_json_multiple_rows() {
173        let mut formatter = JsonFormatter::new();
174        let mut output = Vec::new();
175
176        let columns = test_columns();
177        formatter.write_header(&mut output, &columns).unwrap();
178
179        let row1 = Row::new(vec![Value::Int(1), Value::Text("Alice".to_string())]);
180        let row2 = Row::new(vec![Value::Int(2), Value::Text("Bob".to_string())]);
181
182        formatter.write_row(&mut output, &row1).unwrap();
183        formatter.write_row(&mut output, &row2).unwrap();
184
185        formatter.write_footer(&mut output).unwrap();
186
187        let result = String::from_utf8(output).unwrap();
188
189        // Verify it's valid JSON
190        let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
191        assert!(parsed.is_array());
192        assert_eq!(parsed.as_array().unwrap().len(), 2);
193    }
194
195    #[test]
196    fn test_json_null_value() {
197        let mut formatter = JsonFormatter::new();
198        let mut output = Vec::new();
199
200        let columns = test_columns();
201        formatter.write_header(&mut output, &columns).unwrap();
202
203        let row = Row::new(vec![Value::Int(1), Value::Null]);
204        formatter.write_row(&mut output, &row).unwrap();
205
206        formatter.write_footer(&mut output).unwrap();
207
208        let result = String::from_utf8(output).unwrap();
209        assert!(result.contains("\"name\": null"));
210    }
211
212    #[test]
213    fn test_json_supports_streaming() {
214        let formatter = JsonFormatter::new();
215        assert!(formatter.supports_streaming());
216    }
217}