Rust in Everything
基于 serde_json 1.0.154

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 一节的实验输出)。

📝NOTE
没有蠢问题

问: 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

to_value:

// 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:三种内部表示

Number 包着一个私有的 N:

#[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 一致。

📝NOTE
冷知识: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 -0 in 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 也一样。

📝NOTE
冷知识: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 下先把键值对按键排序再哈希。
❗IMPORTANT
动脑筋:同一个 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_string 4 次,全是输出 Vec 从 128 字节长到 1024 字节。to_value 91 次:外层数组 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。

💬 关联讨论

发起讨论
加载中...