u128 is not supported
主线位置:缓冲主线。内部标签、untagged、flatten 先把输入读成
Content再重放,而Content没有 128 位整数;重放时u128字段的deserialize_u128落到 trait 的默认实现,就是这一句。序列化主线上也有一处缓冲(内部标签或 flatten 里的 struct 变体),同样会撞上默认实现。
flowchart TB
field["u128 字段"] --> where{"在哪里"}
where -->|"直接读"| fmt["格式的 deserialize_u128"]
fmt -->|"json yaml bincode"| ok["Ok"]
fmt -->|"toml urlencoded"| bad["默认实现报错"]
where -->|"内部标签 flatten"| ct["Content 重放"]
ct --> bad
where -->|untagged| nm["no match 一句话"]
Content 有哪些变体、读入和重放各少了什么,在 Content;这一页只讲碰到之后怎么办。
本页实验在 M2 Max、macOS、rustc 1.99.0 上运行,依赖 serde = "=1.0.229"(derive)、serde_json = "=1.0.154"、bincode = "=1.3.3"、postcard = "1"(1.1.3)、serde_yaml = "=0.9.34"、serde_urlencoded = "=0.7.1"、toml = "0.8"(0.8.23)、serde_with = "3"(3.24.0)。
现象
src/bin/u128.rs:同一个 Big { n: u128 },直接读、放进内部标签、untagged、flatten,以及 u128 在外层、旁边有个 flatten 字段:
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Big {
n: u128,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "t")]
enum Internal {
V(Big),
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
enum Untagged {
V(Big),
}
#[derive(Serialize, Deserialize, Debug)]
struct Flat {
id: u32,
#[serde(flatten)]
big: Big,
}
// u128 in the outer struct, next to a flatten field.
#[derive(Serialize, Deserialize, Debug)]
struct OuterBig {
n: u128,
#[serde(flatten)]
rest: std::collections::HashMap<String, u32>,
}
fn main() {
let max = u128::MAX;
println!("deserialize, n = 7");
de::<Big>("plain", r#"{"n":7}"#);
de::<Internal>("internal", r#"{"t":"V","n":7}"#);
// ... 相邻标签的两行留给动脑筋
de::<Untagged>("untagged", r#"{"n":7}"#);
de::<Flat>("flatten", r#"{"id":1,"n":7}"#);
de::<OuterBig>("outer+flat", r#"{"n":7,"x":1}"#);
println!("deserialize, n = u128::MAX");
de::<Big>("plain", &format!(r#"{{"n":{max}}}"#));
de::<Internal>("internal", &format!(r#"{{"t":"V","n":{max}}}"#));
}
de 是 serde_json::from_str 加打印。输出(相邻标签两行在页末):
deserialize, n = 7
plain Ok(Big { n: 7 })
internal Err(u128 is not supported)
untagged Err(data did not match any variant of untagged enum Untagged)
flatten Err(u128 is not supported at line 1 column 14)
outer+flat Ok(OuterBig { n: 7, rest: {"x": 1} })
deserialize, n = u128::MAX
plain Ok(Big { n: 340282366920938463463374607431768211455 })
internal Err(u128 is not supported)
- 直接读没有问题,连
u128::MAX都能读:serde_json 自己实现了deserialize_u128。 - 内部标签、flatten 里连
7都不行。 失败和值的大小无关,只和「有没有经过Content」有关。 - untagged 把它藏起来了,只剩一句 no match。真正的原因要按 untagged-no-match 的办法逐个变体去试。
u128在外层就没事:有 flatten 的 struct 只把没认出的键缓冲进Content(见 flatten),认出的n照常从 serde_json 直接读。被 flatten 的那部分里的u128才读不回。
出处
数据模型的基本类型里,只有 128 位整数的方法在 trait 里带默认实现,默认就是报错。Deserializer::deserialize_u128:
fn deserialize_u128<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
let _ = visitor;
Err(Error::custom("u128 is not supported"))
}
Serializer::serialize_u128 是同样的一句,i128 的两个方法报 i128 is not supported。这两个类型是 1.0.60 才加进数据模型的,默认实现让已有的格式不用改代码就能编译,代价是不支持的格式到运行期才报错(来历见 serde 解决什么问题)。
Content 的重放用的 ContentDeserializer / ContentRefDeserializer 没有覆盖 deserialize_u128,读入用的 ContentVisitor 也没有 visit_u128,Content 里根本没有放 128 位整数的变体。所以只要值经过缓冲,就是这句报错,见 Content 的「丢掉了什么」。
Error::custom 造的错误不带位置。flatten 那行的 at line 1 column 14 是 serde_json 在外层 deserialize_map 返回前补的,指向整个对象的结尾;内部标签的重放在 serde_json 返回之后,连这个位置都没有。
问: 给 Content 加一个 U128 变体不就好了?为什么一直没加?
答: 有人提过,也有人做过。issue #1682「Untagged enum does not play nice with u128」开于 2019 年,issue #2230「Implement visit_i128 and visit_u128 for ContentVisitor」开于 2022 年,两个都还开着。#2230 的作者写道,他的快速实现「ends up breaking multiple libraries, including serde_json, that use the Unexpected::* enum」。补上 flatten 和内部标签的 PR #2781(「Add support for 128-bit integers in flatten structs and internally tagged enums」)2024 年 7 月提交,到 1.0.229 仍未合并。对照一下:serde_with 3.24.0 自己那份 Content 拷贝里就有 U128(u128) 变体,但那只在 serde_with 的 PickFirst 一类适配器里用,不影响 serde 的 derive。
序列化一侧也有一次缓冲
序列化通常不缓冲,内部标签的 newtype 变体包一个 struct 时,TaggedSerializer 把 serialize_struct 直接转给真正的格式,多写一个标签字段就行。但如果内部包的是一个外部标签 enum 的 struct 变体,或者 flatten 一个这样的 enum:
// Serialization side: an externally tagged struct variant inside an internally tagged newtype.
#[derive(Serialize, Debug)]
enum Shape {
S { n: u128 },
}
#[derive(Serialize, Debug)]
#[serde(tag = "t")]
enum Wrap {
V(Shape),
}
#[derive(Serialize, Debug)]
struct FlatShape {
#[serde(flatten)]
shape: Shape,
}
serialize, n = 7
plain Ok({"n":7})
internal Ok({"t":"V","n":7})
untagged Ok({"n":7})
flatten Ok({"id":1,"n":7})
tag+struct v Err(u128 is not supported)
flat enum Err(u128 is not supported)
这两种要写出 {"t":"V","S":{"n":7}} / {"S":{"n":7}}:在一个已经打开的 map 里写一个键 "S",再写它的值。SerializeMap::serialize_value 要一个完整的值,struct 变体的字段却是一个一个经 serialize_field 来的。TaggedSerializer::serialize_struct_variant 于是返回 SerializeStructVariantAsMapValue,把每个字段先用 ContentSerializer 变成 Content 攒起来,end 时再整体写出(no_std 版本的注释说得直白:「Lack of push-based serialization means we need to buffer the content of the struct variant」)。flatten 走 FlatMapSerializer::serialize_struct_variant,同理。ContentSerializer 也没有覆盖 serialize_u128,于是用了默认实现。
格式自己不支持
不经过缓冲,也要看格式实现了没有。同一个程序的最后一段,Big { n: u128::MAX }(toml 另试了 n = 7):
let b = Big { n: max };
println!(" serde_json {}", r(serde_json::to_string(&b)));
println!(" json Value {}", r(serde_json::to_value(&b)));
println!(" json Value7 {}", r(serde_json::to_value(&Big { n: 7 })));
let bytes = bincode::serialize(&b).unwrap();
println!(" bincode {} back {}", bytes.len(), r(bincode::deserialize::<Big>(&bytes)));
let bytes = postcard::to_allocvec(&b).unwrap();
println!(" postcard {} back {}", bytes.len(), r(postcard::from_bytes::<Big>(&bytes)));
// ... serde_yaml、toml、serde_urlencoded 同样写出再读回
formats, Big { n: u128::MAX } and plain u128::MAX
serde_json Ok("{\"n\":340282366920938463463374607431768211455}")
json Value Err(number out of range)
json Value7 Ok(Object {"n": Number(7)})
bincode 16 back Ok(Big { n: 340282366920938463463374607431768211455 })
postcard 19 back Ok(Big { n: 340282366920938463463374607431768211455 })
serde_yaml Ok("n: 340282366920938463463374607431768211455\n")
back Ok(Big { n: 340282366920938463463374607431768211455 })
toml Err(u128 is not supported)
toml 7 Err(u128 is not supported)
toml de Err(TOML parse error at line 1, column 5
|
1 | n = 7
| ^
u128 is not supported
)
urlencoded Ok("n=340282366920938463463374607431768211455")
back Err(u128 is not supported)
| 格式 | 写 u128 | 读 u128 |
|---|---|---|
| serde_json 1.0.154 | 支持 | 支持 |
serde_json::to_value | 放得进 u64 才行,否则 number out of range | — |
| bincode 1.3.3 | 支持(16 字节) | 支持 |
| postcard 1.1.3 | 支持(varint,19 字节) | 支持 |
| serde_yaml 0.9.34 | 支持 | 支持 |
| toml 0.8.23 | u128 is not supported,7 也不行 | u128 is not supported |
| serde_urlencoded 0.7.1 | 支持 | u128 is not supported |
toml 0.8.23 和它用的 toml_edit 0.22.27 源码里没有出现 u128,两个方法都是默认实现。serde_urlencoded 0.7.1 的写出一侧实现了 serialize_u128,读的一侧给值用的 Part 反序列化器只为 bool、u8…u64、i8…i64、f32、f64 写了解析(de.rs 里的 forward_parsed_value!),deserialize_u128 落到默认实现:能写不能读。axum 的 Query 和 Form 都把解析交给 serde_urlencoded::Deserializer(见 Path 与 Query),查询参数里的 u128 字段因此读不出来;axum 自己的 PathDeserializer 写了 deserialize_u128。
修法
写成字符串。 字符串经得起任何缓冲,也是 toml、查询字符串这类格式里唯一的选择。用 serde_with::DisplayFromStr:
#[serde_as]
#[derive(Serialize, Deserialize, Debug)]
struct BigStr {
#[serde_as(as = "DisplayFromStr")]
n: u128,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "t")]
enum InternalStr {
V(BigStr),
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
enum UntaggedStr {
V(BigStr),
}
fix: u128 as a string
internal Ok({"t":"V","n":"340282366920938463463374607431768211455"})
internal Ok(V(BigStr { n: 340282366920938463463374607431768211455 }))
untagged Ok(V(BigStr { n: 340282366920938463463374607431768211455 }))
JavaScript 的 Number 只能精确表示 2^53 以内的整数,对外的 JSON 接口里大整数写成字符串本来就更稳。代价是格式变了:{"n":7} 这样的旧输入不再被接受(要两种都收,见 invalid type: string "1", expected u32 的 PickFirst 和手写 deserialize_any)。
避开缓冲。 只要不经过 Content,u128 就交给格式处理:
- 外部标签(默认表示)不缓冲,见 外部标签。
- flatten 的 struct 里,把
u128字段挪到外层(上面的outer+flat)。 - 相邻标签缓冲不缓冲取决于输入里标签和内容的顺序,下面的动脑筋。
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "t", content = "c")]
enum Adjacent {
V(Big),
}
de::<Adjacent>("adj tag 1st", r#"{"t":"V","c":{"n":7}}"#);
de::<Adjacent>("adj tag 2nd", r#"{"c":{"n":7},"t":"V"}"#);
同一个值,两种键的顺序,各是 Ok 还是 u128 is not supported?答案在页末。
怎么确认没中招
- 搜一遍
u128/i128字段,看它所在的类型有没有被用在#[serde(tag = ...)]、#[serde(untagged)]的变体里,或者被#[serde(flatten)]进别的 struct;再看序列化一侧有没有「内部标签的 newtype 包外部标签的 struct 变体」。 - 往返测试要覆盖 enum 和 flatten 的那一层,不能只测内层 struct:上面的
Big单独测完全没问题。 - untagged 里的
u128不会报这句,只会 no match。把输入读成Value,再用from_value对各个变体单独试,能看到u128 is not supported。 - 配置文件和查询参数:用 toml 0.8 或 serde_urlencoded(包括 axum 的
Query、Form)读的类型里不要有u128/i128,或者用字符串表示。
动脑筋答案
adj tag 1st Ok(V(Big { n: 7 }))
adj tag 2nd Err(u128 is not supported at line 1 column 21)
- 标签在前,读得回来。 生成的
visit_map先读到"t",知道是变体V,接下来"c"的值直接按Big从 serde_json 读,u128字段由 serde_json 的deserialize_u128处理。 - 内容在前,读不回来。 读到
"c"时还不知道变体,只能先把{"n":7}缓冲成Content,等读到标签再用ContentDeserializer重放,于是撞上默认实现。位置column 21是整个对象的结尾,那是 serde_json 返回前补上的。 - 两种顺序都是合法输入,哪种出现由写 JSON 的一方决定。serde 自己序列化相邻标签时总是标签在前,所以自己写、自己读的往返测试永远是绿的;对方如果按键名排序写出 JSON,
"c"就排到了"t"前面。相邻标签的两条路径见 内部标签与相邻标签。
小结
serialize_u128/deserialize_u128的默认实现报u128 is not supported;Content没有 128 位整数,ContentDeserializer和ContentSerializer都用了默认实现。- 内部标签、flatten 进来的部分、内容在标签之前的相邻标签:读不回,连
7都不行;untagged 只报 no match。直接读、外部标签、flatten 外层的字段不受影响。 - 序列化一侧,内部标签或 flatten 里的外部标签 struct 变体要先缓冲字段,也会报这句。
- toml 0.8.23 读写都不支持,serde_urlencoded 0.7.1 能写不能读(axum 的
Query、Form因此读不了u128),serde_json::to_value超过u64报number out of range。 - 修法:
DisplayFromStr写成字符串,或者让u128不经过缓冲。