You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Serde序列化字符串(枚举转CSV场景)时避免添加双引号?

问题描述

我试图将一个枚举序列化为CSV的两个连续字段:第一个字段是依枚举变体而定的固定字符串,第二个字段是枚举变体中的值并附加“Hz”后缀。我已为该枚举实现了Display trait以生成预期格式,再使用SerializeDisplay进行序列化,但生成的CSV字段被添加了不必要的双引号。

代码示例:

use serde_with::SerializeDisplay;
use std::fmt;

#[derive(SerializeDisplay)]
pub enum Tone {
    Up(String),
    Off,
}

impl fmt::Display for Tone {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Tone::Up(s) => write!(f, "TONE,{}Hz", s),
            Tone::Off => write!(f, "OFF,87.5Hz"),
        }
    }
}

fn main() {
        let tone = Tone::Up("hi".to_string());
        let mut wtr = csv::WriterBuilder::new()
            .has_headers(false)
            .from_writer(vec![]);
        wtr.serialize(tone).unwrap();
        let csv = String::from_utf8(wtr.into_inner().unwrap()).unwrap();
        assert_eq!(csv, "TONE,hiHz\n");
}

运行错误:

assertion `left == right` failed
  left: "\"TONE,hiHz\"\n"
 right: "TONE,hiHz\n"
解决方案

问题根源在于:你通过Display把两个CSV字段拼成了一个带逗号的字符串,而CSV规范中,包含逗号、换行或引号的字段必须用双引号包裹,所以writer自动添加了引号。正确的做法是让枚举直接序列化为两个独立字段,而非拼接成单一字符串。

方法一:手动实现Serialize trait(推荐)

放弃SerializeDisplay,手动实现Serialize,让枚举序列化为两个字段的元组,这样CSV writer会输出标准的双字段内容,无需额外引号。

修改后的代码:

use serde::Serialize;
use std::fmt;

#[derive(Debug)]
pub enum Tone {
    Up(String),
    Off,
}

// 保留Display实现(如果需要)
impl fmt::Display for Tone {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Tone::Up(s) => write!(f, "TONE,{}Hz", s),
            Tone::Off => write!(f, "OFF,87.5Hz"),
        }
    }
}

// 实现Serialize,将枚举转为双字段元组
impl Serialize for Tone {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let (field1, field2) = match self {
            Tone::Up(s) => ("TONE", format!("{}Hz", s)),
            Tone::Off => ("OFF", "87.5Hz".to_string()),
        };
        (field1, field2).serialize(serializer)
    }
}

fn main() {
    let tone = Tone::Up("hi".to_string());
    let mut wtr = csv::WriterBuilder::new()
        .has_headers(false)
        .from_writer(vec![]);
    wtr.serialize(tone).unwrap();
    let csv = String::from_utf8(wtr.into_inner().unwrap()).unwrap();
    assert_eq!(csv, "TONE,hiHz\n"); // 断言通过
}

方法二:强制CSV writer禁用引号(不推荐)

如果一定要沿用Display拼接字符串的方式,可以配置CSV writer永不添加引号,但这会破坏CSV规范——若字段后续出现换行、引号等特殊字符,生成的CSV将无法被正确解析。

修改后的代码:

use serde_with::SerializeDisplay;
use std::fmt;

#[derive(SerializeDisplay)]
pub enum Tone {
    Up(String),
    Off,
}

impl fmt::Display for Tone {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Tone::Up(s) => write!(f, "TONE,{}Hz", s),
            Tone::Off => write!(f, "OFF,87.5Hz"),
        }
    }
}

fn main() {
    let tone = Tone::Up("hi".to_string());
    let mut wtr = csv::WriterBuilder::new()
        .has_headers(false)
        .quote_style(csv::QuoteStyle::Never) // 强制禁用所有引号
        .from_writer(vec![]);
    wtr.serialize(tone).unwrap();
    let csv = String::from_utf8(wtr.into_inner().unwrap()).unwrap();
    assert_eq!(csv, "TONE,hiHz\n"); // 断言通过
}

内容的提问来源于stack exchange,提问作者caiman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 06:00:10