与人聊天时偶然问到一个问题:“给定任意一个(C/C++)结构体,如何实现其JSON的序列化和反序列化,而不用专门编写相应的序列化、反序列化实现代码?”我摇摇头,表示不知如何实现这一功能;现在我也认为,这一功能对于C/C++,是不可能自动化实现的。不过对于其他的静态编译型编程语言,如Golang/Rust等,这一功能则相对容易实现。与Golang的反射机制(Reflection)不同,Rust使用到了trait
机制。
serde提供了通用的序列化功能,诸多具体的数据组织、序列化库的实现都依赖该库,如JSON,MessagePack等。基于此库也可以实现自定义的序列化功能,不过很少有人这样做:现有的数据序列化格式众多,选择一个适合的格式往往事半功倍。Rust的JSON库为serde_json,二者配合使用可以实现Rust结构化的自动序列化和反序列化。笔者编写的简单测试例程在Cargo.toml
工程配置文件中的依赖为:
serde = { version = "1.0.127", features = ["derive"] }
serde_json = "1.0.66"
笔者指定了二者的具体版本。注意上面的features = ["deriver"]
语句,指示在编译serde库时,需要的特性为derive
。
serde库提供了对Rust基本数据类型的序列化功能,并通过Serialize
和Deserailize
特性trait
向外部提供。只要结构体中的(基本)数据类型都支持这两个特性,那么Rust会通过serde自动组织任一结构体的序列化和反序列化功能。以下是笔者定义的简单结构体:
use std::fs;
use serde_json;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct CityWeather {
name: String,
temp: [i32; 2],
temps: Vec,
}
通过serde_json::to_string(...)
函数可以实现任一实现了Serialize
特性的结构体的JSON数据的序列化。该接口的详细说明见官方文档,以下是笔者编写的序列化测试代码:
let beijing = CityWeather {
name: "Beijing".to_string(),
temp: [23, 31],
temps: vec![31, 30, 32, 30, 30],
};
let sdata = serde_json::to_string(&beijing);
if sdata.is_err() {
println!("Error, failed to serialize structure: {}", sdata.unwrap_err());
std::process::exit(1);
}
let sdata = sdata.unwrap();
println!("Serialized data:\n{}", sdata);
编译运行的结果如下:
Serialized data:
{"name":"Beijing","temp":[23,31],"temps":[31,30,32,30,30]}
使用C/C++对JSON字符串反序列化会生成一个链表,或者Json::value
之类的对象,而不会生成任意的结构体变量。而Rust可以轻松地实现这一点,反序列化可以生成不同类型的结构体变量。笔者以下JSON数据测试:
{"name":"Shanghai","temp":[24,33],"temps":[33,34,33,32,32,31,35]}
以上数据保存于test.json
的文本文件中,反序列化得到CityWeather
结构体变量的代码如下:
let jdata = fs::read_to_string("test.json");
if jdata.is_err() {
println!("Error, cannot read 'test.json': {}", jdata.unwrap_err());
std::process::exit(2);
}
let jdata = jdata.unwrap();
let jvalue = serde_json::from_str(&jdata);
if jvalue.is_err() {
println!("Error, invalid json data: {}", jdata);
std::process::exit(3);
}
let jvalue = jvalue.unwrap();
let weather = serde_json::from_value::(jvalue);
if weather.is_err() {
println!("Error, cannot parse '{}' as CityWeather: {:?}",
jdata, weather.unwrap_err());
std::process::exit(4);
}
let weather = weather.unwrap();
println!("Json data from file:\nCity name: {}, temperature: {:?}, prediction: {:?}",
weather.name, weather.temp, weather.temps);
编译运行结果如下:
Json data from file:
City name: Shanghai, temperature: [24, 33], prediction: [33, 34, 33, 32, 32, 31, 35]
由此可见,Rust提供的序列化功能库可以很大程度上提升开发效率。
注意到,笔者定义的CityWeather
中的temp
变量,是一个长度为固定2的数组,记录一天当中的最低漫和最高温:
temp: [i32; 2],
它不是一个向量Vec,包含的数据不可增长。如果尝试使用以下数据反序列化,并尝试得到CityWeather
结构体变量,上面的代码可以检测到该异常:
{"name":"Shanghai","temp":[24,33,28],"temps":[33,34,33,32,32,31,35]}
temp
数组有三个元素,这一数据不能反序列化为CityWeather
变量:
Error, cannot parse '{"name":"Shanghai","temp":[24,33,28],"temps":[33,34,33,32,32,31,35]}
' as CityWeather: Error("invalid length 3, expected fewer elements in array", line: 0, column: 0)
由此可见,serde的反序列化功能是很健壮、稳定的。