fix: handle array-of-arrays in CSV formatter (#288)
* fix: handle array-of-arrays in CSV formatter The CSV formatter assumed all items were JSON objects when collecting column names. APIs that return arrays of arrays (e.g. Sheets values) produced empty newlines instead of data. Add an early return path for non-object arrays that emits each inner array's elements as CSV cells directly, mirroring the table formatter's existing "array of non-objects" handling. Fixes #283 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add coverage for flat scalar CSV formatting --------- Co-authored-by: Dan Rassi <129646+drassi@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@googleworkspace/cli": patch
|
||||
---
|
||||
|
||||
Fix `--format csv` for array-of-arrays responses (e.g. Sheets values API)
|
||||
@@ -356,6 +356,23 @@ fn format_csv_page(value: &Value, emit_header: bool) -> String {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
// Array of non-objects
|
||||
if !arr.iter().any(|v| v.is_object()) {
|
||||
let mut output = String::new();
|
||||
for item in arr {
|
||||
if let Value::Array(inner) = item {
|
||||
let cells: Vec<String> = inner
|
||||
.iter()
|
||||
.map(|v| csv_escape(&value_to_cell(v)))
|
||||
.collect();
|
||||
let _ = writeln!(output, "{}", cells.join(","));
|
||||
} else {
|
||||
let _ = writeln!(output, "{}", csv_escape(&value_to_cell(item)));
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// Collect columns
|
||||
let mut columns: Vec<String> = Vec::new();
|
||||
for item in arr {
|
||||
@@ -564,6 +581,47 @@ mod tests {
|
||||
assert!(output.contains("2,world"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_csv_array_of_arrays() {
|
||||
// Sheets API returns {"values": [["col1","col2"], ["a","b"]]}
|
||||
let val = json!({
|
||||
"values": [
|
||||
["Student Name", "Gender", "Class Level"],
|
||||
["Alexandra", "Female", "4. Senior"],
|
||||
["Andrew", "Male", "1. Freshman"]
|
||||
]
|
||||
});
|
||||
let output = format_value(&val, &OutputFormat::Csv);
|
||||
let lines: Vec<&str> = output.lines().collect();
|
||||
assert_eq!(lines[0], "Student Name,Gender,Class Level");
|
||||
assert_eq!(lines[1], "Alexandra,Female,4. Senior");
|
||||
assert_eq!(lines[2], "Andrew,Male,1. Freshman");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_csv_flat_scalars() {
|
||||
// Flat array of non-object, non-array values → one value per line
|
||||
let val = json!(["apple", "banana", "cherry"]);
|
||||
let output = format_value(&val, &OutputFormat::Csv);
|
||||
let lines: Vec<&str> = output.lines().collect();
|
||||
assert_eq!(lines.len(), 3);
|
||||
assert_eq!(lines[0], "apple");
|
||||
assert_eq!(lines[1], "banana");
|
||||
assert_eq!(lines[2], "cherry");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_csv_flat_scalars_with_escaping() {
|
||||
// Scalars that contain commas/quotes must be CSV-escaped
|
||||
let val = json!(["plain", "has,comma", "has\"quote"]);
|
||||
let output = format_value(&val, &OutputFormat::Csv);
|
||||
let lines: Vec<&str> = output.lines().collect();
|
||||
assert_eq!(lines.len(), 3);
|
||||
assert_eq!(lines[0], "plain");
|
||||
assert_eq!(lines[1], "\"has,comma\"");
|
||||
assert_eq!(lines[2], "\"has\"\"quote\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_csv_escape() {
|
||||
assert_eq!(csv_escape("simple"), "simple");
|
||||
|
||||
Reference in New Issue
Block a user