使用Serde读取TOML文件:camelCase与snake_case映射问题
问题:Rust中config crate结合serde实现camelCase到snake_case反序列化失败
我刚接触Rust,正在学习如何在Rust中读取配置。尝试使用config crate结合serde将配置反序列化为结构体:
use config::{Config, File}; use serde::Deserialize; #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct DatabaseConfiguration { connection_string: String } #[derive(Deserialize)] struct Configuration { database: DatabaseConfiguration } fn main() { println!("Hello, world!"); let settings = Config::builder() .add_source(File::with_name("config")) .build() .unwrap(); let config: Configuration = settings.try_deserialize().unwrap(); println!("Configured settings:"); println!("connection string: {:?}", config.database.connection_string); }
对应的TOML文件内容如下:
[database] connectionString = "https://awesome"
根据Serde文档的理解,rename_all = "camelCase"应能将TOML中的connectionString映射到结构体的connection_string,但反序列化时报错:
missing field `connection_string`
将TOML属性改为snake_case并移除rename_all配置后可正常运行,显然我对Serde容器属性的用法存在误解,请问该如何正确实现这一需求?
解决方案
问题出在config crate的默认行为:它会自动将所有配置键标准化为snake_case,也就是说TOML中的connectionString会被转换成connection_string后再传给serde。而你添加的#[serde(rename_all = "camelCase")]会让serde期望输入的键是camelCase格式(即connectionString),两者不匹配导致报错。
有两种可行的解决方式:
方式一:禁用config的键标准化
让config保留原始的键名格式,交给serde处理映射。只需在构建Config时设置命名规范为None:
use config::{Config, File, NamingConvention}; use serde::Deserialize; #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct DatabaseConfiguration { connection_string: String } #[derive(Deserialize)] struct Configuration { database: DatabaseConfiguration } fn main() { println!("Hello, world!"); let settings = Config::builder() .set_naming_convention(NamingConvention::None) // 禁用键标准化 .add_source(File::with_name("config")) .build() .unwrap(); let config: Configuration = settings.try_deserialize().unwrap(); println!("Configured settings:"); println!("connection string: {:?}", config.database.connection_string); }
方式二:利用config的默认转换简化代码
既然config已经自动将camelCase转换为snake_case,直接去掉结构体上的rename_all属性即可,无需额外配置:
use config::{Config, File}; use serde::Deserialize; #[derive(Deserialize)] struct DatabaseConfiguration { connection_string: String } #[derive(Deserialize)] struct Configuration { database: DatabaseConfiguration } fn main() { println!("Hello, world!"); let settings = Config::builder() .add_source(File::with_name("config")) .build() .unwrap(); let config: Configuration = settings.try_deserialize().unwrap(); println!("Configured settings:"); println!("connection string: {:?}", config.database.connection_string); }
如果需要同时兼容其他序列化场景(比如直接用serde解析TOML字符串),推荐使用方式一;如果仅使用config crate读取配置,方式二更简洁。
内容的提问来源于stack exchange,提问作者flq
相关产品推荐
相关产品推荐

