sheeter-engine 0.1.1

A CLI tool for converting data files between formats. v0.1.0 supports CSV <-> JSON.
Documentation
//! Sheeter is a data file conversion library and CLI tool.
//!
//! This crate provides functions to convert between CSV and JSON formats.
//! It is designed to be small, fast, and portable, suitable for use on
//! low-end devices such as Android phones. The core idea is simple: take
//! data in one format, understand its structure, and emit it in another
//! format without unnecessary overhead.
//!
//! # Examples
//!
//! Convert CSV to JSON:
//!
//! ```
//! use sheeter_engine::csv_to_json;
//!
//! let csv = "name,age\nAlice,30\nBob,25";
//! let json = csv_to_json(csv).unwrap();
//! assert_eq!(json, r#"[{"name":"Alice","age":"30"},{"name":"Bob","age":"25"}]"#);
//! ```
//!
//! Convert JSON to CSV:
//!
//! ```
//! use sheeter_engine::json_to_csv;
//!
//! let json = r#"[{"name":"Alice","age":"30"},{"name":"Bob","age":"25"}]"#;
//! let csv = json_to_csv(json).unwrap();
//! assert_eq!(csv, "name,age\nAlice,30\nBob,25\n");
//! ```

use std::io::Cursor;

/// Converts a CSV string into a JSON array of objects.
///
/// CSV (Comma-Separated Values) is a simple tabular format where each line is a
/// row and columns are separated by commas. JSON (JavaScript Object Notation) is
/// a hierarchical data format. This function reads the first row of the CSV as
/// the header (column names) and each subsequent row as a data record. It then
/// builds a JSON array where each element is an object mapping header names to
/// the corresponding cell values as strings. This is useful for feeding CSV data
/// into JSON-based systems or APIs, or for further processing with JSON tools.
///
/// # Errors
///
/// Returns an error if the CSV is malformed (e.g., inconsistent number of columns)
/// or if JSON serialization fails (which is unlikely for simple string maps).
///
/// # Examples
///
/// Basic conversion:
///
/// ```
/// use sheeter_engine::csv_to_json;
///
/// let csv = "name,age\nAlice,30\nBob,25";
/// let json = csv_to_json(csv).unwrap();
/// assert_eq!(json, r#"[{"name":"Alice","age":"30"},{"name":"Bob","age":"25"}]"#);
/// ```
///
/// Single column:
///
/// ```
/// use sheeter_engine::csv_to_json;
///
/// let csv = "id\n1\n2\n3";
/// let json = csv_to_json(csv).unwrap();
/// assert_eq!(json, r#"[{"id":"1"},{"id":"2"},{"id":"3"}]"#);
/// ```
pub fn csv_to_json(csv: &str) -> Result<String, Box<dyn std::error::Error>> {
    let mut rdr = csv::Reader::from_reader(Cursor::new(csv));
    let headers = rdr.headers()?.clone();
    let mut records = Vec::new();
    for result in rdr.records() {
        let record = result?;
        let mut map = serde_json::Map::new();
        for (header, value) in headers.iter().zip(record.iter()) {
            map.insert(
                header.to_string(),
                serde_json::Value::String(value.to_string()),
            );
        }
        records.push(serde_json::Value::Object(map));
    }
    Ok(serde_json::to_string(&records)?)
}

/// Converts a JSON array of objects into a CSV string.
///
/// JSON arrays of objects are a common way to represent tabular data. This function
/// takes such a JSON string and produces a CSV string. The keys of the first object
/// are used as the CSV header row. All objects are expected to have the same keys;
/// if a key is missing in an object, the corresponding CSV cell is left empty.
/// Values are converted to their string representation: strings are written as-is
/// (without quotes), numbers and booleans use their JSON literal forms.
///
/// # Errors
///
/// Returns an error if the JSON is not a valid array of objects, or if CSV writing
/// fails (e.g., due to I/O errors on the internal buffer).
///
/// # Examples
///
/// Basic conversion:
///
/// ```
/// use sheeter_engine::json_to_csv;
///
/// let json = r#"[{"name":"Alice","age":"30"},{"name":"Bob","age":"25"}]"#;
/// let csv = json_to_csv(json).unwrap();
/// assert_eq!(csv, "name,age\nAlice,30\nBob,25\n");
/// ```
///
/// With numbers and booleans:
///
/// ```
/// use sheeter_engine::json_to_csv;
///
/// let json = r#"[{"id":1,"active":true},{"id":2,"active":false}]"#;
/// let csv = json_to_csv(json).unwrap();
/// assert_eq!(csv, "id,active\n1,true\n2,false\n");
/// ```
pub fn json_to_csv(json: &str) -> Result<String, Box<dyn std::error::Error>> {
    let values: Vec<serde_json::Value> = serde_json::from_str(json)?;
    if values.is_empty() {
        return Ok(String::new());
    }
    let first = values[0]
        .as_object()
        .ok_or("JSON array must contain objects")?;
    let headers: Vec<String> = first.keys().cloned().collect();
    let mut wtr = csv::Writer::from_writer(vec![]);
    wtr.write_record(&headers)?;
    for value in values {
        let obj = value.as_object().ok_or("JSON array must contain objects")?;
        let record: Vec<String> = headers
            .iter()
            .map(|h| match obj.get(h) {
                Some(serde_json::Value::String(s)) => s.clone(),
                Some(v) => v.to_string(),
                None => String::new(),
            })
            .collect();
        wtr.write_record(&record)?;
    }
    let data = wtr.into_inner()?;
    Ok(String::from_utf8(data)?)
}