Value
主线位置:
Value在两条主线上各站两次。作为一个类型,它实现Serialize(序列化主线的类型那一端)和Deserialize(反序列化主线的类型那一端,只调deserialize_any);作为一种「格式」,value::Serializer把任何类型序列化成Value(to_value),Value自己实现Deserializer,把它当输入再读成任何类型(from_value)。它和缓冲主线上的Content是同一个思路的公开版:先读成一棵树,再从树上读。
flowchart TB text["JSON 文本"] -->|from_str| val[Value] val -->|"to_string"| text ty["Rust 类型 T"] -->|to_value| val val -->|from_value| ty text -->|"from_str 直接"| ty ty -->|"to_string 直接"| text
这一页讲 serde_json 的 value/(mod.rs、de.rs、ser.rs、index.rs 等)、map.rs、number.rs、raw.rs 和 json! 宏。文本怎么解析成 Value 的那一部分(deserialize_any 按首字节分派、数字解析)在反序列化一侧,Value 写成文本时经过的 Formatter、转义、MapKeySerializer 在序列化一侧。
本页实验都在 M2 Max、macOS、rustc 1.99.0 上跑。默认配置的实验依赖 serde = "=1.0.229"(derive)、serde_json = "=1.0.154";讲 feature 的实验在另一个 crate 里,serde_json 打开 arbitrary_precision、preserve_order、raw_value、unbounded_depth。cargo 会合并同一个依赖的 feature,两种配置不能放在同一个 crate 里比。
Value 的定义
Value(文档注释删掉了):
#[derive(Clone, Eq, PartialEq, Hash)]
pub enum Value {
Null,
Bool(bool),
Number(Number),
String(String),
Array(Vec<Value>),
Object(Map<String, Value>),
}
六个变体对应 JSON 的六种值。Value 和 serde 数据模型的 29 种类型不是一一对应的:没有单独的整数宽度、char、bytes、Option、enum,这些在 to_value 时都会折叠成这六种之一(下面讲 to_value 时看到)。
在默认配置下,size_of::<Value>() 是 32 字节:最大的变体是 String、Vec<Value>、Map(都是 24 字节),加上判别值;Number 是 16 字节。打开 preserve_order 后变成 72 字节(见下文 Map 一节的实验输出)。
问: Value 里有 f64,f64 不能实现 Eq 和 Hash,Value 怎么能 derive 它们?
答: derive 要的是每个字段实现 Eq / Hash,Value 里放的是 Number,不是 f64。Number 内部的浮点变体带着一个不变量:「Always finite」。所有构造 Number 的路径都守着它,Number::from_f64(f64::NAN) 返回 None,Value::from(f64::NAN) 和 json!(f64::NAN) 得到的是 Value::Null。没有 NaN,浮点的 == 就是自反的,impl Eq for N {} 旁边的注释写着「Implementing Eq is fine since any float values are always finite.」剩下一个坑是 +0.0 == -0.0 但两者的位不同,Hash for N 专门把 0 的两种表示哈希成同一个值,保证「相等的值哈希相等」。
作为类型:Serialize 与 Deserialize
Serialize for Value 是一个 match:Null → serialize_unit、Bool → serialize_bool、Number 交给 Number 自己、String → serialize_str、Array 交给 Vec,Object 用 serialize_map(Some(m.len())) 逐个 serialize_entry。
Deserialize for Value 只调一个方法:
deserializer.deserialize_any(ValueVisitor)
Value 不知道输入里是什么,只能请格式告诉它,所以它只能配自描述格式(为什么 bincode 这类格式不行,见 serde 解决什么问题)。ValueVisitor 的 visit_* 各建一种变体:visit_bool → Bool,visit_i64 / visit_u64 / visit_f64 → Number(visit_f64 收到非有限值时得 Null),visit_unit / visit_none → Null,visit_seq 逐个 push 进一个从容量 0 开始的 Vec。
字符串一律复制:
fn visit_str<E>(self, value: &str) -> Result<Value, E>
where
E: serde::de::Error,
{
self.visit_string(String::from(value))
}
Value::String 拥有一个 String,借不了输入。对象的键也一样:visit_map 先用 KeyClassifier 读第一个键,它的 visit_str 对普通键做 s.to_owned()。KeyClassifier 存在是因为第一个键可能是魔法名字:开了 arbitrary_precision 时 $serde_json::private::Number、开了 raw_value 时 $serde_json::private::RawValue 说明这个「对象」其实是一个数字或一段原文(下文讲)。第一个键是普通键,才建 Map、插入第一对,再用 next_entry 读完剩下的。
作为格式:to_value 与 from_value
to_value:value::Serializer
// Taking by value is more friendly to iterator adapters, option and result
// consumers, etc. See https://github.com/serde-rs/json/pull/149.
pub fn to_value<T>(value: T) -> Result<Value, Error>
where
T: Serialize,
{
value.serialize(Serializer)
}
按值拿 T 不会强迫调用者交出所有权:serde 给 &T 实现了 Serialize,传 &x 就行,PR #149 当时的说明就是「This is backwards compatible as there exists an implementation of Serializable for &'a T where T: Serializable.」
这里的 Serializer 是 value::Serializer,一个零大小的 struct Serializer;,Ok = Value。它每个方法直接返回一个 Value:serialize_str 是 Value::String(value.to_owned()),serialize_f64 是 Value::from(float)(非有限值变 Null),serialize_bytes 是一个数字数组,serialize_char 是只有一个字符的 String,unit variant 是变体名字符串。复合类型的状态对象收集子值,SerializeMap 的 Map 变体是:
Map {
map: Map<String, Value>,
next_key: Option<String>,
},
serialize_key 用一个只输出 String 的 MapKeySerializer 把键变成字符串存进 next_key,serialize_value 取出它,map.insert(key, tri!(to_value(value)))。每个子值递归地调一次 to_value,即一个新的零大小 Serializer,这就是 Serialize 与 Serializer 说的「在内存里建树的格式直接给值实现」。哪些键类型能用、和写文本的 MapKeySerializer 有什么不同(Some 键),在序列化一侧的实验里。
from_value:Value 自己是 Deserializer
from_value 只有一行 T::deserialize(value):Value 实现了 serde::Deserializer<'de>。它的 deserialize_any 按变体调 visit_*:
match self {
Value::Null => visitor.visit_unit(),
Value::Bool(v) => visitor.visit_bool(v),
Value::Number(n) => n.deserialize_any(visitor),
#[cfg(any(feature = "std", feature = "alloc"))]
Value::String(v) => visitor.visit_string(v),
#[cfg(not(any(feature = "std", feature = "alloc")))]
Value::String(_) => unreachable!(),
Value::Array(v) => visit_array(v, visitor),
Value::Object(v) => v.deserialize_any(visitor),
}
其余 deserialize_* 不是简单地转给 deserialize_any,而是逐个手写,类型对不上就报 invalid_type。比如 deserialize_option 把 Null 当成 visit_none、其他值当成 visit_some(self);deserialize_enum 接受字符串(unit 变体)或只有一个键的对象;deserialize_struct 同时接受对象和数组。用 forward_to_deserialize_any! 一口气转发的是 Map<String, Value> 和 &Map 这两个 Deserializer。
Value 按值交出,字符串用 visit_string(v) 原样移交;&'de Value 也实现了 Deserializer,它的 deserialize_str 调 visit_borrowed_str(v),借的是 Value 里的 String。src/bin/value_de.rs:
use serde::Deserialize;
use serde_json::{json, Value};
#[derive(Deserialize, Debug)]
#[allow(dead_code)]
struct Borrowing<'a> {
title: &'a str,
}
#[derive(Deserialize, Debug)]
#[allow(dead_code)]
struct Post {
title: String,
n: u32,
}
fn main() {
let v: Value = json!({ "title": "serde", "n": 1 });
let b = Borrowing::deserialize(&v).unwrap();
let inside = match &v["title"] {
Value::String(s) => s.as_ptr() == b.title.as_ptr(),
_ => false,
};
println!("Borrowing::deserialize(&v) = {b:?}, points into v: {inside}");
println!("from_value::<Post>(v) = {:?}", serde_json::from_value::<Post>(v.clone()));
let bad = json!({ "title": 1, "n": "1" });
println!("wrong types = {:?}", serde_json::from_value::<Post>(bad).map_err(|e| e.to_string()));
let arr = json!(["serde", 1]);
println!("array as struct = {:?}", serde_json::from_value::<Post>(arr));
let long = json!(["serde", 1, true]);
println!("array too long = {:?}", serde_json::from_value::<Post>(long).map_err(|e| e.to_string()));
}
Borrowing::deserialize(&v) = Borrowing { title: "serde" }, points into v: true
from_value::<Post>(v) = Ok(Post { title: "serde", n: 1 })
wrong types = Err("invalid type: string \"1\", expected u32")
array as struct = Ok(Post { title: "serde", n: 1 })
array too long = Err("invalid length 3, expected fewer elements in array")
Borrowing里的&str指向v里那个String的缓冲。from_value要求T: DeserializeOwned,借用只能经T::deserialize(&v)。wrong types里两个字段都错,报出来的是n:默认的Map是BTreeMap,按键排序遍历,n在title前面。错误也没有行列号,Value里已经没有位置信息了。- 数组可以读成 struct(按字段顺序),多出的元素由
visit_array在visit_seq返回后检查,报invalid length 3, expected fewer elements in array。
Number:三种内部表示
#[cfg(not(feature = "arbitrary_precision"))]
#[derive(Copy, Clone)]
enum N {
PosInt(u64),
/// Always less than zero.
NegInt(i64),
/// Always finite.
Float(f64),
}
非负整数放 PosInt,负整数放 NegInt,所以一个整数只有一种表示(5 不会既能是 PosInt(5) 又能是 NegInt(5)),派生出来的比较才对。三个变体之间不互相比较:PartialEq for N 只在同一变体内比,PosInt(1) 和 Float(1.0) 不相等。src/bin/numbers.rs:
use serde_json::{json, Number, Value};
fn main() {
println!("size_of::<Number>() = {}", std::mem::size_of::<Number>());
for text in [
"1",
"-1",
"1.0",
"1e2",
"-0",
"-0.0",
"18446744073709551615",
"18446744073709551616",
"0.1000000000000000055511151231257827",
"123456789012345678901234567890",
] {
let n: Number = serde_json::from_str(text).unwrap();
println!(
"{text:<38} Debug={n:?} i64={} u64={} f64={} back={}",
n.is_i64(),
n.is_u64(),
n.is_f64(),
serde_json::to_string(&n).unwrap()
);
}
println!("json!(1) == json!(1.0) {}", json!(1) == json!(1.0));
let big: Value = serde_json::from_str("1e300").unwrap();
println!("Value(1e300) == f32::INFINITY {}", big == f32::INFINITY);
println!("Value(1e300) == f64::INFINITY {}", big == f64::INFINITY);
println!("json!(0.1) == 0.1f32 {}", json!(0.1) == 0.1f32);
println!("json!(0.1f32) == 0.1f64 {}", json!(0.1f32) == 0.1f64);
println!("json!(0.1f32) {}", json!(0.1f32));
println!("json!(f64::NAN) {}", json!(f64::NAN));
println!("Number::from_f64(NAN) {:?}", Number::from_f64(f64::NAN));
let r: Result<u8, _> = serde_json::from_value(json!(300));
println!("from_value::<u8>(300) {:?}", r.map_err(|e| e.to_string()));
}
默认配置下:
size_of::<Number>() = 16
1 Debug=Number(1) i64=true u64=true f64=false back=1
-1 Debug=Number(-1) i64=true u64=false f64=false back=-1
1.0 Debug=Number(1.0) i64=false u64=false f64=true back=1.0
1e2 Debug=Number(100.0) i64=false u64=false f64=true back=100.0
-0 Debug=Number(-0.0) i64=false u64=false f64=true back=-0.0
-0.0 Debug=Number(-0.0) i64=false u64=false f64=true back=-0.0
18446744073709551615 Debug=Number(18446744073709551615) i64=false u64=true f64=false back=18446744073709551615
18446744073709551616 Debug=Number(1.8446744073709552e+19) i64=false u64=false f64=true back=1.8446744073709552e+19
0.1000000000000000055511151231257827 Debug=Number(0.1) i64=false u64=false f64=true back=0.1
123456789012345678901234567890 Debug=Number(1.2345678901234568e+29) i64=false u64=false f64=true back=1.2345678901234568e+29
json!(1) == json!(1.0) false
Value(1e300) == f32::INFINITY false
Value(1e300) == f64::INFINITY false
json!(0.1) == 0.1f32 true
json!(0.1f32) == 0.1f64 false
json!(0.1f32) 0.10000000149011612
json!(f64::NAN) null
Number::from_f64(NAN) None
from_value::<u8>(300) Err("invalid value: integer `300`, expected u8")
- 文本里的写法决定了变体:
1是整数,1.0和1e2是浮点,再写回去就是1.0、100.0。json!(1) == json!(1.0)为假。 -0在默认配置下读成浮点-0.0(NegInt只放小于零的整数,0 带负号放不进去)。- 超出
u64的整数(18446744073709551616)和很长的小数都退成f64,精度在读进来时就丢了,写回去已经不是原文。 is_i64/is_u64/is_f64看的是表示,不是数值:1两个整数判断都是真,1.0只有is_f64是真。Value和原生数字的==由value/partial_eq.rs实现,和f32比时(eq_f32)把Number转成f32再比,所以json!(0.1) == 0.1f32为真;json!(0.1f32)则先把0.1f32转成f64存进去(from_f32里的N::Float(f as f64)),打印出来是0.10000000149011612,和0.1f64比为假。
和 f32 比较用的转换是 Number::as_f32,它是 pub(crate) 的:
pub(crate) fn as_f32(&self) -> Option<f32> {
#[cfg(not(feature = "arbitrary_precision"))]
match self.n {
N::PosInt(n) => Some(n as f32),
N::NegInt(n) => Some(n as f32),
N::Float(n) => Some(n as f32).filter(|float| float.is_finite()),
}
#[cfg(feature = "arbitrary_precision")]
self.n.parse::<f32>().ok().filter(|float| float.is_finite())
}
1e300 在 f64 里是有限的,as f32 却会变成无穷大,.filter(|float| float.is_finite()) 把这种情况变成 None,于是 Value(1e300) == f32::INFINITY 为假,和 == f64::INFINITY 一致。
Value == f32::INFINITY 在 1.0.152 之前可以为真serde-rs/json PR #1347「Value == f32 is false outside the range of f32」,首次发布于 v1.0.152,release notes 写的是「Fix comparison of large non-integer values with f32::INFINITY」。之前,默认配置下的 as_f32 三个分支都是 Some(n as f32)。PR 的说明:
「Without arbitrary_precision, Number::as_f32 cast with as f32, which rounds a finite number outside the range of f32 to infinity, so let v: Value = serde_json::from_str("1e300")?; v == f32::INFINITY // true v == f64::INFINITY // false」
「The arbitrary_precision build already filtered non-finite results (parse::<f32>().ok().filter(is_finite)), so the two builds disagreed. Apply the same filter to both.」
现在两种配置的 as_f32 都是上面那段代码,Value 里放不进无穷大(Number 只存有限值),和无穷大比较就不该为真。上面实验的 Value(1e300) == f32::INFINITY false 就是修过之后的结果。
arbitrary_precision:N 变成 String
打开 arbitrary_precision 后,N 是另一个定义:
#[cfg(feature = "arbitrary_precision")]
type N = String;
数字原样存成文本,Number::as_str 可以取出来。同一个 numbers.rs 在打开 feature 的 crate 里跑:
size_of::<Number>() = 24
1 Debug=Number(1) i64=true u64=true f64=false back=1
-1 Debug=Number(-1) i64=true u64=false f64=false back=-1
1.0 Debug=Number(1.0) i64=false u64=false f64=true back=1.0
1e2 Debug=Number(1e+2) i64=false u64=false f64=true back=1e+2
-0 Debug=Number(-0) i64=true u64=false f64=false back=-0
-0.0 Debug=Number(-0.0) i64=false u64=false f64=true back=-0.0
18446744073709551615 Debug=Number(18446744073709551615) i64=false u64=true f64=false back=18446744073709551615
18446744073709551616 Debug=Number(18446744073709551616) i64=false u64=false f64=false back=18446744073709551616
0.1000000000000000055511151231257827 Debug=Number(0.1000000000000000055511151231257827) i64=false u64=false f64=true back=0.1000000000000000055511151231257827
123456789012345678901234567890 Debug=Number(123456789012345678901234567890) i64=false u64=false f64=false back=123456789012345678901234567890
json!(1) == json!(1.0) false
Value(1e300) == f32::INFINITY false
Value(1e300) == f64::INFINITY false
json!(0.1) == 0.1f32 true
json!(0.1f32) == 0.1f64 true
json!(0.1f32) 0.1
json!(f64::NAN) null
Number::from_f64(NAN) None
from_value::<u8>(300) Err("invalid number")
- 大整数和长小数原样保留;
-0保留成整数-0(PR #1346,1.0.152 的发布说明是「Preserve sign of-0in arbitrary_precision mode」)。Number变成 24 字节(一个String)。 - 一个超出
u64和i64的整数三个is_*都是假:它不能无损地转成任何一种原生类型。 1e2写回去是1e+2,不是原文(下面的冷知识)。json!(0.1f32)存的是 zmij 格式化f32得到的"0.1",于是和0.1f64相等了,和默认配置相反。from_value::<u8>(300)的报错变成了一句没有细节的invalid number。这个配置下Number的deserialize_u8等方法由deserialize_number!生成,函数体是visitor.$visit(tri!(self.n.parse().map_err(|_| invalid_number()))):"300".parse::<u8>()失败,原因被换成了invalid_number()。
数字是怎么以字符串的身份穿过 serde 数据模型的?数据模型里没有「任意精度数字」这种类型,Number 的 Serialize 于是把自己伪装成一个 struct:
let mut s = tri!(serializer.serialize_struct(TOKEN, 1));
tri!(s.serialize_field(TOKEN, &self.n));
s.end()
TOKEN 是 "$serde_json::private::Number"。serde_json 的写出器在 serialize_struct 里认出这个名字,返回 Compound::Number,它的 serialize_field 用 NumberStrEmitter 把字符串不加引号地写出去(序列化一侧列过这两个魔法名字)。别的格式不认识这个名字,看到的就是一个名叫 $serde_json::private::Number、只有一个字符串字段的 struct。反方向同理,解析器把数字交成一个只有这一个键的 map,Value 的 KeyClassifier 认出键名,才把它变回 Number。这层伪装经过缓冲主线时会出问题(untagged、内部标签、flatten 先把 map 读进 Content),见 Content。
cargo 会合并依赖图里所有人对同一个 crate 打开的 feature:依赖树里任何一个 crate 打开了 arbitrary_precision,所有用 serde_json 的代码看到的都是 N = String,上面那些行为差异会落在没有打开它的代码上。preserve_order 也一样。
arbitrary_precision 从 1.0.149 起会改写指数Cargo.toml 里对这个 feature 的说明是「this feature makes JSON -> serde_json::Number -> JSON produce output identical to the input」,但上面 1e2 读回来是 1e+2。
serde-rs/json PR #1306「Align arbitrary_precision number strings with zmij’s formatting」,首次发布于 v1.0.149。说明是:「When serde_json switched float formatting from ryu to zmij, serialized floats always include an explicit + in positive exponents (e.g. 1.23e+45). However, numbers parsed via the arbitrary_precision feature keep the literal source text, so 1.23e45 stayed without the +. That meant the same numeric value round-tripped to different strings depending on whether it flowed through zmij Value::from(1.23e45) or through the arbitrary_precision parser」,改法是「if an exponent lacks an explicit sign we insert a +」。
现在 de.rs 里 arbitrary_precision 版的 scan_exponent 无论原文是 e 还是 E 都写入 'e',没有符号时补一个 '+'。下文 RawValue 实验里 Number from 1E2 打印出 1e+2 也是这个原因。保留下来的是数值的全部位数,不是原文的每一个字符。
Map 与 preserve_order
Map 包着一个按 feature 切换的 MapImpl:
pub struct Map<K, V> {
map: MapImpl<K, V>,
}
#[cfg(not(feature = "preserve_order"))]
type MapImpl<K, V> = BTreeMap<K, V>;
#[cfg(feature = "preserve_order")]
type MapImpl<K, V> = IndexMap<K, V, BuildHasherImpl>;
#[cfg(all(feature = "preserve_order", feature = "std"))]
type BuildHasherImpl = std::collections::hash_map::RandomState;
#[cfg(all(feature = "preserve_order", not(feature = "std")))]
type BuildHasherImpl = foldhash::fast::RandomState;
默认是 BTreeMap:键按字典序排列,序列化出来的对象键是排好序的。preserve_order 换成 IndexMap:按插入顺序。没有 std 时用 foldhash 的 RandomState(1.0.154 起 preserve_order 不再隐含 std,PR #1333「Support preserve_order in no_std builds」),Map 的模块文档说它「provides minimal HashDoS resistance, but does not protect against interactive attackers」。Map 的方法大多直接转发,差别集中在顺序相关的地方。remove:
pub fn remove<Q>(&mut self, key: &Q) -> Option<Value>
where
String: Borrow<Q>,
Q: ?Sized + Ord + Eq + Hash,
{
#[cfg(feature = "preserve_order")]
return self.swap_remove(key);
#[cfg(not(feature = "preserve_order"))]
return self.map.remove(key);
}
IndexMap 删除元素有两种做法:swap_remove 把最后一个元素挪进空位,O(1);shift_remove 把后面的元素整体前移,O(n)。remove 选了前者,文档提醒「If you need to preserve the relative order of the keys in the map, use .shift_remove(key) instead.」
src/bin/order.rs:
use serde_json::{Map, Value};
fn main() {
let input = r#"{"title":"t","b":1,"a":2,"id":3}"#;
let v: Value = serde_json::from_str(input).unwrap();
println!("parse + print {v}");
let mut m: Map<String, Value> = serde_json::from_str(input).unwrap();
m.remove("title");
println!("after remove(title) {}", Value::Object(m.clone()));
m.insert("title".into(), "t".into());
println!("after insert(title) {}", Value::Object(m));
let dup: Value = serde_json::from_str(r#"{"a":1,"b":2,"a":3}"#).unwrap();
println!("duplicate key a {dup}");
let mut s: Value = serde_json::from_str(input).unwrap();
s.sort_all_objects();
println!("sort_all_objects {s}");
let x: Value = serde_json::from_str(r#"{"a":1,"b":2}"#).unwrap();
let y: Value = serde_json::from_str(r#"{"b":2,"a":1}"#).unwrap();
println!("reordered == equal {}", x == y);
}
默认配置:
parse + print {"a":2,"b":1,"id":3,"title":"t"}
after remove(title) {"a":2,"b":1,"id":3}
after insert(title) {"a":2,"b":1,"id":3,"title":"t"}
duplicate key a {"a":3,"b":2}
sort_all_objects {"a":2,"b":1,"id":3,"title":"t"}
reordered == equal true
- 读进来再写出去,键已经排了序,原来排在第一的
title跑到了最后。 - 重复的键后者覆盖前者(
Map::insert的行为),不报错。 - 两个只是键顺序不同的对象相等。
preserve_order下也是如此:PartialEq for Map转给IndexMap的==,后者不看顺序;为了和它一致,Hash for Map在preserve_order下先把键值对按键排序再哈希。
order.rs 换到打开 preserve_order 的 crate 里跑,前三行打印什么?M2 Max、macOS、rustc 1.99.0,serde_json = { version = "=1.0.154", features = ["arbitrary_precision", "preserve_order", "raw_value", "unbounded_depth"] }。提示:title 原本是第一个键,remove 在这个配置下是哪一种删除?
preserve_order 的代价在大小上。同一个打印 size_of 的程序(下一节的 value_basics.rs)在打开 feature 的 crate 里输出的第一行是:
size_of: Value=72 Number=24 Map=72 String=24 Vec<Value>=24
Map 从 BTreeMap 的 24 字节变成 72 字节(IndexMap 加上它的哈希器状态),Value 跟着从 32 变成 72,数组里的每个元素都按这个大小占位,不管它是不是对象。
索引:value["a"]["b"]
Value 可以用 [] 取下标,参数是实现了 Index 的类型:usize(数组)、str / String(对象)、以及它们的引用。这个 trait 是封住的(private::Sealed),外面不能再给它加实现。它有三个方法:index_into 和 index_into_mut 找不到返回 None,index_or_insert 找不到就插入或 panic。
ops::Index 的实现找不到时不 panic,返回一个静态的 Null:
fn index(&self, index: I) -> &Value {
static NULL: Value = Value::Null;
index.index_into(self).unwrap_or(&NULL)
}
所以 v["x"]["y"][9] 一路都是 Null,不会出错;代价是「键不存在」和「键存在、值为 null」用 [] 分不出来,要区分得用 get(返回 Option)。IndexMut 用的是 index_or_insert:对象里没有这个键就插入一个 Null;对 Null 用字符串下标,先把它变成空对象;对数字、字符串这些用字符串下标,或者数组下标越界,就 panic。src/bin/value_basics.rs:
use serde_json::{json, Map, Number, Value};
use std::mem::size_of;
fn main() {
println!(
"size_of: Value={} Number={} Map={} String={} Vec<Value>={}",
size_of::<Value>(),
size_of::<Number>(),
size_of::<Map<String, Value>>(),
size_of::<String>(),
size_of::<Vec<Value>>()
);
let v: Value = serde_json::from_str(r#"{"b":{"c":[10,20]},"a":1}"#).unwrap();
println!("to_string {}", v);
println!("v[\"b\"][\"c\"][1] {}", v["b"]["c"][1]);
println!("v[\"x\"][\"y\"][9] {}", v["x"]["y"][9]);
println!("v[\"a\"][\"b\"] {}", v["a"]["b"]);
println!("v.get(\"x\") {:?}", v.get("x"));
println!("v.pointer(\"/b/c/0\") {:?}", v.pointer("/b/c/0"));
let mut w = json!({});
w["p"]["q"] = json!(true);
println!("w after w[p][q]=true {}", w);
let r = std::panic::catch_unwind(|| {
let mut n = json!(1);
n["k"] = json!(2);
});
println!("index_mut on number panicked={}", r.is_err());
let r = std::panic::catch_unwind(|| {
let mut a = json!([1]);
a[5] = json!(2);
});
println!("index_mut a[5] panicked={}", r.is_err());
}
默认配置:
size_of: Value=32 Number=16 Map=24 String=24 Vec<Value>=24
to_string {"a":1,"b":{"c":[10,20]}}
v["b"]["c"][1] 20
v["x"]["y"][9] null
v["a"]["b"] null
v.get("x") None
v.pointer("/b/c/0") Some(Number(10))
w after w[p][q]=true {"p":{"q":true}}
thread 'main' (2682687) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_json-1.0.154/src/value/index.rs:102:18:
cannot access key "k" in JSON number
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
index_mut on number panicked=true
thread 'main' (2682687) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_json-1.0.154/src/value/index.rs:72:21:
cannot access index 5 of JSON array of length 1
index_mut a[5] panicked=true
v["a"]是数字1,再取["b"]:index_into对非对象返回None,结果是Null,不 panic。w["p"]["q"] = true:w["p"]插入一个Null,["q"]把这个Null变成对象再插入,一行建出两层。- 写入时的两种 panic 消息来自
index.rs的panic!(第 102 行和第 72 行)。panic 消息在 stderr,println!在 stdout,两者交错的顺序是终端里看到的顺序。 pointer按 JSON Pointer(RFC 6901)的路径语法取值,找不到返回None。
json! 宏
json! 是一个 macro_rules! 宏,转给内部的 json_internal!,用一个个规则逐个「吃」token:null、true、false、[...]、{...} 直接生成对应的 Value 变体,其余的一切走最后一条规则:
// Any Serialize type: numbers, strings, struct literals, variables etc.
// Must be below every other rule.
($other:expr) => {
$crate::to_value(&$other).unwrap()
};
对象的键则用 .into() 转成 String。src/bin/json_macro.rs:
use serde::Serialize;
use serde_json::json;
use std::collections::BTreeMap;
#[derive(Serialize)]
struct Tag {
name: &'static str,
}
fn main() {
let id = 7;
let key = "dyn";
let v = json!({
"id": id,
"tags": [Tag { name: "rust" }, null, 1.5],
key: true,
(format!("k{}", id)): "x",
});
println!("{v}");
let mut by_pair = BTreeMap::new();
by_pair.insert((1, 2), "a");
let r = std::panic::catch_unwind(|| json!({ "m": by_pair }));
println!("tuple-key map inside json!: panicked={}", r.is_err());
}
{"dyn":true,"id":7,"k7":"x","tags":[{"name":"rust"},null,1.5]}
thread 'main' (2633884) panicked at src/bin/json_macro.rs:23:39:
called `Result::unwrap()` on an `Err` value: Error("key must be a string", line: 0, column: 0)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
tuple-key map inside json!: panicked=true
cargo expand 展开第一个 json!,摘出关键几行:
let v = ::serde_json::Value::Object({
let mut object = ::serde_json::Map::new();
let _ = object.insert(("id").into(), ::serde_json::to_value(&id).unwrap());
// ...
::serde_json::to_value(&Tag { name: "rust" }).unwrap(),
::serde_json::Value::Null,
::serde_json::to_value(&1.5).unwrap(),
// ...
let _ = object.insert((key).into(), ::serde_json::Value::Bool(true));
// ...
object
});
字面量
null、true直接变成Value::Null、Value::Bool(true),不经过to_value;变量、数字、struct 都是to_value(&x).unwrap()。键可以是任何能
.into()成String的表达式:变量key的值"dyn"成了键,加括号的(format!(...))也行。整数不行,json!({ 1: "one" })是编译错误:error[E0277]: the trait bound `std::string::String: From<{integer}>` is not satisfied --> src/bin/json_int_key.rs:4:11 | 4 | let v = json!({ 1: "one" }); | ^^^^^^^^^^^^^^^^^^^ the trait `From<{integer}>` is not implemented for `std::string::String` | = help: `std::string::String` implements trait `From<T>`: From<&mut str> From<&std::string::String> From<&str> From<Box<str>> From<Cow<'_, str>> From<char> = note: required for `{integer}` to implement `Into<std::string::String>` = note: this error originates in the macro `$crate::json_internal` which comes from the expansion of the macro `json` (in Nightly builds, run with -Z macro-backtrace for more info)而
HashMap<u32, _>交给to_value时,u32键会被MapKeySerializer写成"1"。宏里的键是 Rust 代码层面的String,值才走 serde。值里的
unwrap()意味着to_value一失败就 panic:带 tuple 键的 map 放进json!,编译通过,运行时 panic 在宏展开的位置。对来路不明的值,直接调to_value处理Result。
RawValue:不解析的那一段
打开 raw_value 后有 RawValue:
#[repr(transparent)]
pub struct RawValue {
json: str,
}
它是一个动态大小类型,内容就是一段合法的 JSON 文本。#[repr(transparent)] 让 &str 和 &RawValue 的布局相同,from_borrowed 才能 transmute。使用时是 &'a RawValue(借用输入)或 Box<RawValue>(拥有一份拷贝)。
反序列化又是一个魔法名字。Deserialize for &'a RawValue 调 deserializer.deserialize_newtype_struct(TOKEN, ReferenceVisitor),TOKEN 是 "$serde_json::private::RawValue"。serde_json 的 deserialize_newtype_struct 认出这个名字,改走 deserialize_raw_value:
fn deserialize_raw_value<V>(&mut self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
tri!(self.parse_whitespace());
self.read.begin_raw_buffering();
tri!(self.ignore_value());
self.read.end_raw_buffering(visitor)
}
跳过前导空白,记下起点,用 ignore_value 完整地扫过一个值(语法照样检查),再把起点到当前位置的这一段交给 visitor。SliceRead 的 begin_raw_buffering 只记一个下标,结束时切出 &self.slice[start..self.index],可以借用;IoRead 的同名方法开一个 Vec,边读边存,只能拷贝,所以从 reader 读只能用 Box<RawValue>。三种 Read 的区别在反序列化一侧。序列化时 RawValue 也伪装成以 TOKEN 为名的 struct,写出器用 RawValueStrEmitter 原样写出。src/bin/raw.rs(在打开 feature 的 crate 里):
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
#[derive(Deserialize, Serialize)]
struct Envelope<'a> {
kind: &'a str,
#[serde(borrow)]
payload: &'a RawValue,
}
#[derive(Deserialize)]
struct Owned {
payload: Box<RawValue>,
}
#[derive(Deserialize, Debug)]
#[serde(untagged)]
#[allow(dead_code)]
enum Either {
Raw { payload: Box<RawValue> },
}
fn main() {
let input = r#"{"kind":"post", "payload": { "b" : [1, 2.50, 1E2] } }"#;
let env: Envelope = serde_json::from_str(input).unwrap();
let start = env.payload.get().as_ptr() as usize - input.as_ptr() as usize;
println!("payload.get() {:?}", env.payload.get());
println!("borrowed from input offset={start} len={}", env.payload.get().len());
println!("serialize back {}", serde_json::to_string(&env).unwrap());
let owned: Owned = serde_json::from_reader(input.as_bytes()).unwrap();
println!("from_reader Box {:?}", owned.payload.get());
let n: serde_json::Number = serde_json::from_str("1E2").unwrap();
println!("Number from 1E2 {n}");
// No numbers in this input, so arbitrary_precision plays no part.
let no_numbers = r#"{"payload":{"b":"x"}}"#;
println!("untagged + RawValue {:?}", serde_json::from_str::<Either>(no_numbers).map(|_| ()).map_err(|e| e.to_string()));
println!("RawValue::from_string(\"[1,\") {:?}", RawValue::from_string("[1,".into()).map(|_| ()).map_err(|e| e.to_string()));
}
payload.get() "{ \"b\" : [1, 2.50, 1E2] }"
borrowed from input offset=27 len=24
serialize back {"kind":"post","payload":{ "b" : [1, 2.50, 1E2] }}
from_reader Box "{ \"b\" : [1, 2.50, 1E2] }"
Number from 1E2 1e+2
untagged + RawValue Err("data did not match any variant of untagged enum Either")
RawValue::from_string("[1,") Err("EOF while parsing a value at line 1 column 3")
payload指向输入的第 27 个字节,24 字节长:前导空白被跳过,内部的空白、2.50的尾零、1E2的大写全部保留,写回去一字不差。这是RawValue和arbitrary_precision的区别:后者保留的是每个数字的位数(指数会被改写),前者保留整段文本。payload字段上的#[serde(borrow)]不能省。derive 只对&str、&[u8](以及包在Option里的这两种)自动加'de: 'a(生命周期 'de 与借用);去掉它,src/bin/raw_noborrow.rs编译不过:use serde::Deserialize; use serde_json::value::RawValue; #[derive(Deserialize)] #[allow(dead_code)] struct Envelope<'a> { payload: &'a RawValue, } fn main() { let e: Envelope = serde_json::from_str(r#"{"payload":[1]}"#).unwrap(); println!("{}", e.payload); }error: lifetime may not live long enough --> src/bin/raw_noborrow.rs:7:3 | 4 | #[derive(Deserialize)] | ----------- lifetime `'de` defined here 5 | #[allow(dead_code)] 6 | struct Envelope<'a> { | -- lifetime `'a` defined here 7 | payload: &'a RawValue, | ^^^^^^^ requires that `'de` must outlive `'a` | = help: consider adding the following bound: `'de: 'a` error: could not compile `exp-serde-json1-features` (bin "raw_noborrow") due to 2 previous errors放进 untagged enum 就失败了,即使输入里没有数字。untagged 先把整个对象读进
Content,再对Content重放;Content是一棵已经解析过的树,那一段原文已经不存在了,RawValue要的deserialize_newtype_struct(TOKEN)拿不到文本。缓冲主线怎么丢掉格式特有的信息,见 untagged 和 Content。RawValue::from_string会完整解析一遍来校验,非法的 JSON 拿不到RawValue。
RawValue 适合「这一段我不关心,原样转发」:外层用 derive 的类型解析出路由信息,payload 不解析、不分配(借用时),转发给下游时一字不改。
Value 还是 derive 的类型
同一份文档,读成 derive 的类型和读成 Value 各要多少次分配?src/bin/cost.rs,计数分配器同时数 alloc 和 realloc 及其字节数:
#[derive(Serialize, Deserialize)]
struct Post {
id: u32,
title: String,
tags: Vec<String>,
draft: bool,
}
fn main() {
let posts: Vec<Post> = (0..10)
.map(|i| Post { id: i, title: format!("post {i}"), tags: vec!["rust".into(), "json".into()], draft: false })
.collect();
let text = serde_json::to_string(&posts).unwrap();
println!("document: {} bytes, 10 posts x 4 fields", text.len());
measure("from_str::<Vec<Post>>", || serde_json::from_str::<Vec<Post>>(&text).unwrap());
let value = measure("from_str::<Value>", || serde_json::from_str::<Value>(&text).unwrap());
measure(" then from_value::<Vec<Post>>", || serde_json::from_value::<Vec<Post>>(value).unwrap());
let value = serde_json::from_str::<Value>(&text).unwrap();
measure(" or Vec::<Post>::deserialize(&v)", || Vec::<Post>::deserialize(&value).unwrap());
measure("to_string(&posts)", || serde_json::to_string(&posts).unwrap());
let v = measure("to_value(&posts)", || serde_json::to_value(&posts).unwrap());
measure(" then to_string(&value)", || serde_json::to_string(&v).unwrap());
}
(measure 在闭包前后读计数器、打印差值;计数分配器和序列化一侧的 writes.rs 相同,另外累加了每次申请的字节数。)跑两次,输出相同:
document: 631 bytes, 10 posts x 4 fields
from_str::<Vec<Post>> allocs= 43 bytes= 2668
from_str::<Value> allocs= 93 bytes= 8796
then from_value::<Vec<Post>> allocs= 11 bytes= 1040
or Vec::<Post>::deserialize(&v) allocs= 41 bytes= 1180
to_string(&posts) allocs= 4 bytes= 1920
to_value(&posts) allocs= 91 bytes= 7580
then to_string(&value) allocs= 4 bytes= 1920
逐行对账:
from_str::<Vec<Post>>,43 次。 每个Post有title一个String、tags一个Vec、两个标签各一个String,10 个就是 40 次;外层Vec<Post>从 0 开始push,容量 4、8、16 是 3 次。id和draft不分配,字段名只用来匹配__Field,不留下来。from_str::<Value>,93 次,字节数是三倍多。 多出来的主要是键:每个对象 4 个键,每个键一个String,共 40 次;每个对象的BTreeMap再各分配一个节点,10 次。剩下的 43 次和上面一样(title、tags的Vec<Value>、标签字符串、外层数组的 3 次增长)。字节数大还因为每个元素按Value的 32 字节占位。- 再
from_value,11 次。 字符串都是visit_string移交过去的,不复制;外层Vec<Post>一次分配到位,因为Value的SeqDeserializer知道还剩几个元素,size_hint给出精确值;10 个tags各一次。 - 从
&Value读,41 次。 借着读,字符串只能复制:30 个String加 11 个Vec。 to_string4 次,全是输出Vec从 128 字节长到 1024 字节。to_value91 次:外层数组 1 次(with_capacity(10)),每个Post一个BTreeMap节点、4 个键、title、tags的Vec、两个标签,共 9 次。Value再to_string,和直接to_string一样是 4 次。
所以「先读成 Value 再 from_value」一共 104 次分配,是直接读的 2.4 倍;「先 to_value 再 to_string」是 95 次对 4 次。Value 的代价是它要把键也存下来、要给每个值一个统一大小的槽位,并且不能借用输入。
什么时候用 Value:
- 结构事先不知道或经常变:插件配置、外部系统的回调、只想取其中一两个字段(
v["data"]["id"])。 - 要在运行时修改再写回:增删字段、合并两个文档。需要保留原来的键顺序时打开
preserve_order,并留意上面remove的行为和它对整个依赖图的影响。 - 测试里写预期值,
json!比构造 struct 方便。
什么时候用 derive 的类型:结构已知,就让 serde 直接读进目标类型。不需要中间树,字段名不留下,&str / Cow 字段还可以借用输入。只是想跳过一段不关心的内容时,用 IgnoredAny(只扫过,不建树,见标准库类型的实现)或 RawValue(保留原文),都比 Value 便宜。
动脑筋答案
打开 preserve_order 的 crate 里跑 order.rs:
parse + print {"title":"t","b":1,"a":2,"id":3}
after remove(title) {"id":3,"b":1,"a":2}
after insert(title) {"id":3,"b":1,"a":2,"title":"t"}
duplicate key a {"a":3,"b":2}
sort_all_objects {"a":2,"b":1,"id":3,"title":"t"}
reordered == equal true
- 第一行:
IndexMap按插入顺序,原样保留输入的顺序。 - 第二行:
remove在这个配置下是swap_remove,把最后一个键id挪进title空出的第 0 个位置,b、a不动,于是id跑到了最前面。想保持其余键的相对顺序,用shift_remove。 - 第三行:
insert一个新键追加在末尾,title回不到原来的位置。 - 后三行和默认配置相同:重复键的值被覆盖、但位置留在第一次出现的地方(
a仍在b前面);sort_all_objects在preserve_order下按键排序,结果和BTreeMap的一样;相等比较不看顺序。
小结
Value有六个变体,默认 32 字节;它实现Serialize/Deserialize(后者只调deserialize_any,字符串和键都复制),又通过value::Serializer和自己的Deserializer实现充当格式:to_value建树、from_value从树上读,字符串按值移交、按引用借用。Number的N是PosInt(u64)/NegInt(i64)/Float(f64),浮点只存有限值,所以Value能Eq+Hash;1和1.0不相等,超出u64的整数退成浮点。1.0.152(#1347)起Value == f32对超出f32范围的数为假。arbitrary_precision把N换成String,靠$serde_json::private::Number这个魔法 struct 名字穿过数据模型;数字的位数全部保留,但指数会被改写成e+(1.0.149,#1306),整数转换失败只报invalid number,经过Content缓冲会出问题。Map默认BTreeMap(键排序),preserve_order换成IndexMap(插入顺序,Value变成 72 字节);它的remove是swap_remove,会打乱顺序;相等比较两种配置都不看顺序。feature 对整个依赖图生效。value["a"]["b"]找不到就返回静态的Null,不 panic,但分不出「不存在」和「值为 null」;写入时IndexMut会插入键,类型不对或越界就 panic。json!是macro_rules!:字面量直接构造,其余to_value(&x).unwrap();键要能.into()成String,整数键是编译错误。RawValue借助deserialize_newtype_struct的魔法名字记下一段原文,从切片读时借用、从 reader 读时拷贝;它在 untagged / flatten 这类缓冲表示里用不了。- 同一份 631 字节的文档,读成
Value是 93 次分配,读成 derive 的类型是 43 次;结构已知就用 derive 的类型,只想跳过或转发一段时用IgnoredAny或RawValue。