写Rust有一年多了,我是非常喜欢严谨的语言,就连Rust
的Logo看上去都很工业化,具有严谨性。我愿意付出相对多写代码的成本去换取更稳定,更少Runtime
错误。写代码到今天已经有将近十年了,一直想选一款编译型语言,之前有用过Golang,但仍然不太满意的是编译后的语言仍然在运行时出现空指针错误,而Rust是我见过跟众多语言别具一格的做法,没有null类型。是的,你没有看错,在Rust里没有null类型。加之在Rust编译器严谨的模式下,你很难写出在Runtime时有空指针的错误。
今天我们就来说说一个常规性的操作,解析JSON内容,并转换到struct,我们主要使用serde_json
这个包。
添加依赖
我们主要是用serde_json
这个依赖包,首先在Cargo.toml
里添加dependencies
:
Cargo.toml
[dependencies]
serde_json = "1.0"
Rust推荐使用Semantic Versioning
命名版本号号version,格式如下
MAJOR.MINOR.PATCH
按照我的经验,我推荐所有添加到依赖dependencies
里的所有依赖包version部分省去末尾PATCH
比较好,因为MAJOR
和MINOR
固定之后,即使升级到最新PATCH
一般不会出现问题,因为PATCH
是用来修复Bug,以及其它一些不太影响程序主体的变动。在Cargo.toml
里,当你仅仅声明版本号是1.0
时,PATCH
部分会自动获取最新版本号。这也就是为什么在上面的例子中我写了1.0
做为serde_json
依赖版本号
JSON转Value枚举值
接下来,我们就可以使用serde_json
的API去解析JSON了,如果你很懒,不想定义struct,serde_json
也提供了一个枚举值Value
,它枚举出了所有JSON里可能出现的数据类型,包括null,如:
main.rs
use serde_json::{Result, Value};
fn main() -> Result<()> {
let json = r#"
{
"name": "琼台博客",
"age": 30,
"blog": "https://www.qttc.net",
"addr": null
}"#;
let v: Value = serde_json::from_str(json)?;
println!("name = {}", v["name"]);
println!("age = {}", v["age"]);
println!("blog = {}", v["blog"]);
println!("addr = {}", v["addr"]);
Ok(())
}
Output
$ cargo run
Compiling simple-json v0.1.0 (/Users/nicholas/rust/simple-json)
Finished dev [unoptimized + debuginfo] target(s) in 0.58s
Running `target/debug/simple-json`
name = "琼台博客"
age = 30
blog = "https://www.qttc.net"
addr = null
JSON转Struct
通常,我是尽量使用struct接收转换结果,这样在后续使用时可以减少运行时的错误,在编译阶段能发现低级错误。我们首先要在Cargo.toml
里添加serde
依赖包,它主要专门处理序列化与反序列化的。
Cargo.toml
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
按照以上的例子,我们先定义一个名为User的struct用来接收结果,大概如下
struct User {
name: String,
age: u8,
blog: String,
addr: String,
}
这里的age
我用u8
类型,因为描述一个人的年龄使用一个字节就足够了,Rust没有null
类型,所以上面例子中addr
字段必须把null
去掉改成一个字符串,否则会报错,最终完整代码如下
main.rs
use serde::{Deserialize, Serialize};
use serde_json::{Result};
#[derive(Serialize, Deserialize)]
struct User {
name: String,
age: u8,
blog: String,
addr: String,
}
fn main() -> Result<()> {
let json = r#"
{
"name": "琼台博客",
"age": 30,
"blog": "https://www.qttc.net",
"addr": "4114 Sepulveda Blvd"
}"#;
let u: User = serde_json::from_str(json)?;
println!("name = {}", u.name);
println!("age = {}", u.age);
println!("blog = {}", u.blog);
println!("addr = {}", u.addr);
Ok(())
}
Output
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/simple-json`
name = 琼台博客
age = 30
blog = https://www.qttc.net
addr = 4114 Sepulveda Blvd
JSON字段名与Struct不匹配怎么办
Rust提倡使用Snake-Case
蛇式方式命名变量,但有时候JSON的个别字段是Camel-Case
驼峰式命名,如:
user.json
{
"fullName": "琼台博客",
"age": 30,
"blog": "https://www.qttc.net",
"addr": "4114 Sepulveda Blvd"
}
上面的JSON数据里,其中fullName
是Camel-Case
驼峰式命名,那么你的struct里也要有一个与它同名的字段才能解析转换,如
struct User {
fullName: String,
age: u8,
blog: String,
addr: String,
}
此时,Rust会给你一个告警
structure field `fullName` should have a snake case name
note: `#[warn(non_snake_case)]` on by default
help: convert the identifier to snake case: `full_name`rustc(non_snake_case)
提示你使用Snake-Case
蛇式命名struct里的fullName
字段,或者通过#[warn(non_snake_case)]
把告警关了。虽然告警不影响程序编译运行,但我通常不喜欢改掉默认规则,于是还有另外一种方法完美解决这个问题,使用rename
特性,如
struct User {
#[serde(rename = "fullName")]
full_name: String,
age: u8,
blog: String,
addr: String,
}
rename = "fullName"
表示从JSON中读取fullName
字段。最后完整的例子如下
main.rs
use serde::{Deserialize, Serialize};
use serde_json::{Result};
#[derive(Serialize, Deserialize)]
struct User {
#[serde(rename = "fullName")]
full_name: String,
age: u8,
blog: String,
addr: String,
}
fn main() -> Result<()> {
let json = r#"
{
"fullName": "琼台博客",
"age": 30,
"blog": "https://www.qttc.net",
"addr": "4114 Sepulveda Blvd"
}"#;
let u: User = serde_json::from_str(json)?;
println!("full name = {}", u.full_name);
println!("age = {}", u.age);
println!("blog = {}", u.blog);
println!("addr = {}", u.addr);
Ok(())
}
Output
$ cargo run
Compiling simple-json v0.1.0 (/Users/nicholas/rust/simple-json)
Finished dev [unoptimized + debuginfo] target(s) in 0.81s
Running `target/debug/simple-json`
full name = 琼台博客
age = 30
blog = https://www.qttc.net
addr = 4114 Sepulveda Blvd
null处理
如果用struct接收JSON转换结果,就自然免不了要处理null
的情况,所以我们要把有可能出现null
的字段使用Rust自带的枚举Option<T>
包裹起来。如
user.json
{
"full_name": "琼台博客",
"age": 30,
"blog": "https://www.qttc.net",
"addr": null
}
以上JSON数据里的addr
字段为null
,如果你仍然使用addr: String
来接收,那么在编译时获得一个错误
Error: Error("invalid type: null, expected a string", line: 6, column: 16)
上面的错误提示类型无效,所以我们的struct也要做相应修改,改用Option<String>
处理null
值
struct User {
full_name: String,
age: u8,
blog: String,
addr: Option<String>,
}
最终代码如下
main.rs
use serde::{Deserialize, Serialize};
use serde_json::{Result};
#[derive(Serialize, Deserialize)]
struct User {
full_name: String,
age: u8,
blog: String,
addr: Option<String>,
}
fn main() -> Result<()> {
let json = r#"
{
"full_name": "琼台博客",
"age": 30,
"blog": "https://www.qttc.net",
"addr": null
}"#;
let u: User = serde_json::from_str(json)?;
println!("full name = {}", u.full_name);
println!("age = {}", u.age);
println!("blog = {}", u.blog);
println!("addr = {}", u.addr.unwrap_or("null".to_string()));
Ok(())
}
Output
$ cargo run
Compiling simple-json v0.1.0 (/Users/nicholas/rust/simple-json)
Finished dev [unoptimized + debuginfo] target(s) in 0.72s
Running `target/debug/simple-json`
full name = 琼台博客
age = 30
blog = https://www.qttc.net
addr = null
null
值的处理搞定