【问题标题】:How do I read CSV data without knowing the structure at compile time?如何在编译时不知道结构的情况下读取 CSV 数据?
【发布时间】:2020-10-16 00:58:37
【问题描述】:

我对 Rust 还很陌生,正在尝试实现某种数据库。用户应该通过给出表名、列名向量和列类型向量(通过枚举实现)来创建表。填写表格应通过指定 csv 文件来完成。但是,这需要在编译时指定表行的结构,如基本示例所示:

#[derive(Debug, Deserialize, Eq, PartialEq)]
struct Row {
    key: u32,
    name: String,
    comment: String
}
use std::error::Error;
use csv::ReaderBuilder;
use serde::Deserialize;
use std::fs;

fn read_from_file(path: &str) -> Result<(), Box<dyn Error>> {
    let data = fs::read_to_string(path).expect("Unable to read file");
    let mut rdr = ReaderBuilder::new()
        .has_headers(false)
        .delimiter(b'|')
        .from_reader(data.as_bytes());
    let mut iter = rdr.deserialize();

    if let Some(result) = iter.next() {
        let record:Row = result?;
        println!("{:?}", record);
        Ok(())
    } else {
        Err(From::from("expected at least one record but got none"))
    }   
}

是否有可能使用通用表信息而不是“行”结构来转换反序列化的结果?是否可以根据列类型的组合大小简单地分配内存并解析其中的记录?我会在 C 中做这样的事情......

【问题讨论】:

  • serde_json 有一个通用的 Value 类型,它提供运行时动态构建和 JSON 映射。 stackoverflow.com/questions/59047280/… 有更多解释。这可能是第一个研究方向。
  • 您的result 具有StringRecord 类型,或多或少可以作为字符串数组处理。
  • @Jmb 没错,我可以将每一行存储为字符串向量,并在每次访问时转换为实际类型。但是,这似乎不是很有效。
  • 所以你的问题不是关于 CSV 读取,而是更多关于“当类型仅在运行时知道时,如何存储不同类型的值?”然后,您想使用 enum 和每种可能类型的变体。
  • 由于您是从 CSV 文件中读取数据,因此您的所有值都是 String 类型。插入时可能会发生特定类型的转换。您可以利用 From 特征来实现这一点。例如,如果您的表 A 需要为 u32 类型的值,那么您可以执行 impl From&lt;String&gt; for u32 (这可能已经实现......不确定)。在插入期间,您可以执行let value_to_be_inserted_in_table_A: u32 = string_value.into(); 您可以在此处阅读更多关于FromInto 特征sjoshid.blog/2020/06/07/from-and-to-traits-in-rust

标签: rust deserialization


【解决方案1】:

是否有可能使用通用表信息而不是“行”结构来转换反序列化的结果?

所有泛型在编译时替换为具体类型。如果您不知道运行时需要的类型,那么“泛型”就不是您所需要的。

是否可以简单地根据列类型的组合大小分配内存并解析其中的记录?我会在 C 中做这样的事情......

我建议改用Box&lt;dyn Any&gt;,以便能够存储任何类型的引用,并且仍然知道它是什么类型。

这种方法的维护成本相当高。您必须在要使用单元格值的任何地方管理每种可能的值类型。另一方面,您不需要每次都解析值,只需在运行时进行一些类型检查即可。

我已经使用std::any::TypeId 来识别类型,但它不能用于match 表达式。您可以考虑使用自定义枚举作为类型标识符。

use std::any::{Any, TypeId};
use std::io::Read;

use csv::Reader;

#[derive(Default)]
struct Table {
    name: String,
    headers: Vec<(String, TypeId)>,
    data: Vec<Vec<Box<dyn Any>>>,
}

impl Table {
    fn add_header(&mut self, header: String, _type: TypeId) {
        self.headers.push((header, _type));
    }

    fn populate_data<R: Read>(
        &mut self,
        rdr: &mut Reader<R>,
    ) -> Result<(), Box<dyn std::error::Error>> {
        for record in rdr.records() {
            let record = record?;
            let mut row: Vec<Box<dyn Any>> = vec![];
            for (&(_, type_id), value) in self.headers.iter().zip(record.iter()) {
                if type_id == TypeId::of::<u32>() {
                    row.push(Box::new(value.parse::<u32>()?));
                } else if type_id == TypeId::of::<String>() {
                    row.push(Box::new(value.to_owned()));
                }
            }
            self.data.push(row);
        }
        Ok(())
    }
}

impl std::fmt::Display for Table {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "Table: {}", self.name)?;
        for (name, _) in self.headers.iter() {
            write!(f, "{}, ", name)?;
        }
        writeln!(f)?;
        for row in self.data.iter() {
            for cell in row.iter() {
                if let Some(&value) = cell.downcast_ref::<u32>() {
                    write!(f, "{}, ", value)?;
                } else if let Some(value) = cell.downcast_ref::<String>() {
                    write!(f, "{}, ", value)?;
                }
            }
            writeln!(f)?;
        }
        Ok(())
    }
}

fn main() {
    let mut table: Table = Default::default();
    table.name = "Foo".to_owned();
    table.add_header("key".to_owned(), TypeId::of::<u32>());
    table.add_header("name".to_owned(), TypeId::of::<String>());
    table.add_header("comment".to_owned(), TypeId::of::<String>());
    let data = "\
key,name,comment
1,foo,foo comment
2,bar,bar comment
";
    let mut rdr = Reader::from_reader(data.as_bytes());
    table.populate_data(&mut rdr).unwrap();
    print!("{}", table);
}

【讨论】:

  • 这正是我想要的。这里肯定有“代码”开销,但可能会有更好的性能。我计划将额外的代码复杂性隐藏在标头中的函数指针后面。
  • Any 通常应尽可能避免。如果表只能包含一组有限的类型(例如整数、浮点数或字符串),那么最好使用枚举。这避免了间接,允许您简化一些操作(例如,您可以为您的枚举实现 Display),并确保您不会忘记使用该值的代码中的类型。
  • 感谢您的提示。我会尝试两个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-30
  • 1970-01-01
  • 2018-01-05
  • 1970-01-01
  • 2018-03-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多