← 中文资源

简体中文版本

将 CSV 记录解析为 Serde 结构体

用 csv 与 serde 把表头映射到类型化结构体,数字解析失败或负数会报错。

阅读英文原文

功能说明

这段代码用 csv 与 serde 把 CSV 记录反序列化成强类型结构体。表头中的 sku、quantity、price_cents 会自动对应同名字段,数字列被解析成 u32,因此负数或非数字会直接产生错误,而不是悄悄变成字符串。

它适合导入库存、账单、指标导出等结构化数据。相比手动按列下标取值,结构体字段让后续逻辑更清晰,也能借助编译器发现字段拼写问题。

关键代码

InventoryRow 派生 Deserialize,字段名与 CSV 表头一致。ReaderBuilder::new().trim(csv::Trim::All) 去掉字段两侧空白,from_reader(io::Cursor::new(csv_data)) 让内存字符串实现 Read。

reader.deserialize() 逐条产出 Result<InventoryRow, csv::Error>,record? 把错误向上传播;只要有一行失败,整个函数返回错误,不会返回部分结果。main 断言两行数据、第一行内容,以及负数数量会失败。

使用注意

需要添加 csv 和 serde(启用 derive)依赖。表头必须与字段名完全匹配,或使用 #[serde(rename = "...")] 映射;多余列默认会被忽略,缺失列会报错。u32 不接受负数和小数,金额应统一用整数分。

当前实现一旦遇到坏行就整体失败;如果希望跳过坏行并收集错误,应在循环中记录行号和错误后继续。CSV 中的引号、换行由库处理,但超大文件应直接使用文件 reader,而不是先读入字符串。

验证方式

示例数据两行,解析后长度为 2,第一行等于 InventoryRow { sku: "A-1", quantity: 5, price_cents: 1200 }。把数量改成 -5 后 parse_inventory 返回错误,断言通过。可以再加入带引号的逗号字段和缺失列,观察 Serde 的错误信息。

代码示例

use serde::Deserialize;
use std::error::Error;
use std::io;

#[derive(Debug, Deserialize, PartialEq)]
struct InventoryRow {
    sku: String,
    quantity: u32,
    price_cents: u32,
}

fn parse_inventory(csv_data: &str) -> Result<Vec<InventoryRow>, Box<dyn Error>> {
    let mut reader = csv::ReaderBuilder::new()
        .trim(csv::Trim::All)
        .from_reader(io::Cursor::new(csv_data));

    let mut rows = Vec::new();
    for record in reader.deserialize() {
        let row: InventoryRow = record?;
        rows.push(row);
    }
    Ok(rows)
}

fn main() -> Result<(), Box<dyn Error>> {
    let data = "sku,quantity,price_cents\nA-1,5,1200\nB-2,3,800\n";
    let rows = parse_inventory(data)?;
    println!("{} rows, first={:?}", rows.len(), rows.first());
    assert_eq!(rows.len(), 2);
    assert_eq!(
        rows[0],
        InventoryRow {
            sku: "A-1".into(),
            quantity: 5,
            price_cents: 1200,
        }
    );

    let bad = "sku,quantity,price_cents\nA-1,-5,1200\n";
    assert!(parse_inventory(bad).is_err(), "negative quantity must fail");
    Ok(())
}