Rust下的结构体反序列化
在之前的一篇文章中,笔者介绍了在Rust
编程语言中,使用serde_json包对结构体的序列化和反序列化的基本操作;重点是在定义结构体时,继承源于serde包的 Deserialize
及Serialize
特性:
use serde_json;
use serde::{Deserialize, Serialize};
#[derive(Serilize, Deserialize, Debug)]
struct rust_struct {
member_0: String,
member_1: String,
member_2: i32,
}
不过直接将JSON字符串反序列化得到结构体,这一过程对数据源要求是严格的,结构体与JSON
数据必须完全对应,数中缺少或增加一个键值对,都会导致反序列化操作失败。而在实际应用中,基于JSON
制定的数据协议常常是不断演进、扩展的,若在JSON
数据增加或删除某些键值,按照原先的结构体进行反序列化,应用就不能够正常解析。为了避免这个问题,我们需要一种更灵活的、动态的反序列化的处理方式。
serde_json的基础变量:Value
serde_json
包提供了用于表示一个基本变量的枚举类型,serde_json::Value,这是一个可递归包含自身的类型,类似于cJSON中的结构体cJSON
的定义:
pub enum Value {
Null,
Bool(bool),
Number(Number),
String(String),
Array(Vec<Value>),
Object(Map<String, Value>),
}
这是一个典型的枚举类型的定义。可以看到,对于JSON
的对象类型(Object
),它用基础库中的表Map对表键值项进行映射。访问或遍历这个表,就可以动态地解析JSON
数据。不过需要注意的是,这种解析方法,会解析JSON
字符串的所有项:对于一个很长的JSON
字符串,尽管我们只需要其中的小部分数据项,但仍需要完整的解析,这会浪费一定的计算资源。值得一提的是,serde_json
有只对部分数据进行解析的功能,可进一步提升数据解析的效率,这里不作探讨。
JSON的动态解析
为方便参考,笔者编写了简单的JSON
数据的动态解析,代码全部内容如下(编译得到dyn-json
执行文件):
/* dyn-json.rs */
use serde_json::Value;
fn dump_json_value(jval: &Value, level: usize) -> i32 {
let jobjs = jval.as_object();
if jobjs.is_none() {
return 0;
}
let jobjs = jobjs.unwrap();
let name = jobjs.get("name");
if name.is_some() {
println!("JSON object has name field: {}", name.unwrap());
} else {
println!("JSON object has no name filed.");
}
let lstr = if level > 0 { "\t".repeat(level) } else { "".to_string() };
for jkey in jobjs.keys() {
let obj = jobjs.get(jkey).unwrap();
if obj.is_array() {
println!("{}{} => ARRAY", lstr, jkey);
} else if obj.is_boolean() {
println!("{}{} => {}", lstr, jkey, obj.as_bool().unwrap());
} else if obj.is_f64() {
println!("{}{} => {}", lstr, jkey, obj.as_f64().unwrap());
} else if obj.is_i64() {
println!("{}{} => {}", lstr, jkey, obj.as_i64().unwrap());
} else if obj.is_null() {
println!("{}{} => NULL", lstr, jkey);
} else if obj.is_u64() {
println!("{}{} => {}", lstr, jkey, obj.as_u64().unwrap());
} else if obj.is_object() {
println!("{}{} =>", lstr, jkey);
dump_json_value(obj, level + 1);
} else if obj.is_string() {
println!("{}{} => {}", lstr, jkey, obj.as_str().unwrap());
} else {
println!("{}{} => UNKNOWN TYPE", lstr, jkey);
}
}
return 0;
}
fn main() {
let filename = std::env::args().nth(1).unwrap_or("./sample.json".to_string());
let filedata = std::fs::read_to_string(&filename);
if filedata.is_err() {
eprintln!("{}: {:?}", filename, filedata.err());
std::process::exit(1);
}
let filedata = filedata.unwrap();
let jsondata= serde_json::from_str::<Value>(&filedata);
if jsondata.is_err() {
eprintln!("Error, failed to parse jsondata: {}", filedata);
std::process::exit(1);
}
let jsondata = jsondata.unwrap();
dump_json_value(&jsondata, 0);
}
笔者使用以下数据源sample.json
进行测试:
{
"name": "John Doe",
"age": 43,
"address": {
"street": "10 Downing Street",
"city": "London"
},
"phones": [
"+44 1234567",
"+44 2345678"
],
"testnull": null,
"testint": -1,
"the": 2021,
"rust": 1.60,
"programming": 68719476736,
"language": true
}
测结果如下:
# ./target/release/dyn-json ./samples.json
JSON object has name field: "John Doe"
address =>
JSON object has no name filed.
city => London
street => 10 Downing Street
age => 43
language => true
name => John Doe
phones => ARRAY
programming => 68719476736
rust => 1.6
testint => -1
testnull => NULL
the => 2021
可见,Rust
下的JSON
数据动态解析流程与cJSON
的解析流程很接近:这样的数据解析方式是灵活的,能够避免因数据格式变化造成Rust
结构体反序列化失败的问题。