属性
主线位置:改的是两条主线上 derive 一侧的代码。
rename、skip_serializing_if、serialize_with改变序列化主线里serialize_field的键和值;alias、default、deny_unknown_fields改变反序列化主线里__Field怎么认键、缺了怎么办;flatten、tag、untagged把类型推上缓冲主线。本页讲属性在哪一层、怎么被解析、名字规则怎么算;每个属性让生成代码变成什么样,在生成的 Serialize、生成的 Deserialize 和 04 enum 的表示与缓冲。
flowchart TB src["#[serde(...)] 属性"] --> cont["attr::Container"] src --> var["attr::Variant"] src --> fld["attr::Field"] cont --> rules[rename_all 规则] rules --> var rules --> fld var --> mn["MultiName"] fld --> mn mn --> gen["生成代码里的键"]
代码在 serde_derive/src/internals/:attr.rs(1 818 行,绝大部分是三个 from_ast 里的 if meta.path == ... else if ... 分支)、case.rs(大小写规则)、name.rs(MultiName)、symbol.rs(属性名常量)。解析在派生宏的流水线里的 Container::from_ast 这一步发生,错误进同一个 Ctxt。实验在 M2 Max、macOS、rustc 1.99.0 上跑,依赖 serde = "=1.0.229"(derive)、serde_json = "=1.0.154"。
三层
#[serde(...)] 写在哪里,就由哪个 from_ast 解析:类型上的归 attr::Container::from_ast,enum 变体上的归 attr::Variant::from_ast,字段上的(包括 enum 变体里的字段)归 attr::Field::from_ast。三个函数各有一张自己认识的属性表,表以外的名字一律报错,报错文字也分三种(unknown serde container attribute、unknown serde variant attribute、unknown serde field attribute)。按 1.0.229 的源码,三张表是:
| 层 | 认识的属性 |
|---|---|
| 容器 | rename、rename_all、rename_all_fields(只能用在 enum)、transparent、deny_unknown_fields、default / default = "..."(只能用在有字段的 struct)、bound、untagged(只能用在 enum)、tag、content、from、try_from、into、remote、field_identifier、variant_identifier、crate、expecting |
| 变体 | rename、alias、rename_all、skip、skip_serializing、skip_deserializing、other、bound、with、serialize_with、deserialize_with、borrow(只能用在 newtype 变体)、untagged |
| 字段 | rename、alias、default / default = "..."、skip、skip_serializing、skip_deserializing、skip_serializing_if、serialize_with、deserialize_with、with、bound、borrow、getter、flatten |
同名的属性在不同层意思不同:容器上的 rename_all 改的是字段名(struct)或变体名(enum),变体上的 rename_all 改的是这个 struct 变体里的字段名;容器上的 default 用 Default::default() 补整个 struct 缺的字段,字段上的 default 只补自己。untagged 在容器上是整个 enum 不带标签,在变体上是只有这个变体不带标签(这样的变体必须都排在最后,enum_from_ast 检查)。
容器一级还顺带读两个不是 #[serde] 的属性:#[non_exhaustive](循环里遇到非 serde 属性时检查 matches!(&attr.meta, syn::Meta::Path(path) if path == NON_EXHAUSTIVE))和 #[repr(packed)](单独一个循环扫 repr(...) 里的 packed)。前者只在 remote 派生的 enum 上起作用:生成的 Serialize 多一个兜底分支,遇到本地定义里没有的变体时报错;后者让序列化一侧用 &{self.field} 先复制再取引用,pretend 代码改用 addr_of!。
主要属性的效果
下面一个程序把常用的属性各用一次(src/bin/tour.rs,只摘类型定义):
#[derive(Serialize, Deserialize, Debug)]
struct Post {
#[serde(serialize_with = "upper", deserialize_with = "trimmed")]
title: String,
#[serde(default = "default_lang")]
lang: String,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
tags: Vec<String>,
#[serde(skip)]
cache: Option<u64>,
#[serde(flatten)]
meta: Meta, // struct Meta { views: u32 }
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
struct Strict {
a: u8,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(transparent)]
struct Meters(f64);
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(try_from = "String", into = "String")]
struct Slug(String); // TryFrom<String> 拒绝带空格的字符串
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
enum Status {
Draft,
#[serde(rename = "live")]
Published,
#[serde(other)]
Unknown,
}
upper 把字符串转大写再 serialize_str,trimmed 读一个 String 再 trim,default_lang 返回 "zh"。main 依次反序列化 {"title":" hi ","views":3,"cache":9},再序列化回去,然后测另外几个类型:
Post de: Post { title: "hi", lang: "zh", tags: [], cache: None, meta: Meta { views: 3 } }
Post ser: {"title":"HI","lang":"zh","views":3}
Strict: Err(Error("unknown field `b`, expected `a`", line: 1, column: 10))
Meters: 1.5 / Ok(Meters(2.5))
Slug: "a-b" / Err(Error("slug \"a b\" contains a space", line: 0, column: 0))
Status: "draft" "live" / Ok([Draft, Published, Unknown])
| 属性 | 这次运行里看到的效果 | 生成代码讲在哪 |
|---|---|---|
serialize_with / deserialize_with | title 写出 "HI"、读入时去掉了空格 | 生成的 Serialize(__SerializeWith)、生成的 Deserialize |
default = "..." | 输入没有 lang,填了 default_lang() 的 "zh" | 生成的 Deserialize |
skip_serializing_if + default | tags 为空,输出里没有这个键;读入时缺了也不报错 | 生成的 Serialize |
skip | 输入里的 "cache":9 被当成未知键忽略,字段是 None(skip_deserializing 隐含 default) | 生成的 Deserialize |
flatten | views 和 title 在同一层 | flatten |
deny_unknown_fields | 多了一个键 b 就报 unknown field | 生成的 Deserialize |
transparent | Meters(1.5) 写成 1.5:直接调内部 f64 的 serialize,不经过 serialize_newtype_struct | 生成的 Serialize |
try_from / into | 经 String 转一道,TryFrom 的错误原样成为反序列化错误 | 生成的 Serialize、生成的 Deserialize |
rename_all / rename / other | 变体名小写,Published 改叫 live,不认识的 "archived" 落到 Unknown | 下一节;other 在外部标签 |
另外几个没放进这个程序:tag / content / untagged 决定 enum 的表示,见 04 enum 的表示与缓冲;bound 和 borrow 改的是 impl 的泛型和 'de 约束,见泛型与 bound 和生命周期 'de 与借用;remote / getter 和 crate 的展开见派生宏的流水线。
skip 的那一行来自 Field::from_ast 末尾的一段:
// If skip_deserializing, initialize the field to Default::default() unless a
// different default is specified by `#[serde(default = "...")]` on
// ourselves or our container (e.g. the struct we are in).
if container_default.is_none() && skip_deserializing.0.value.is_some() {
default.set_if_none(Default::Default);
}
skip 不是一个独立的标志,解析时直接展开成 skip_serializing.set_true(...) 加 skip_deserializing.set_true(...);with = "m" 同样直接展开成 serialize_with = m::serialize 加 deserialize_with = m::deserialize。后果在下面的「重复」一节。
名字:rename、rename_all、alias
一个字段或变体在格式里叫什么,存在 MultiName 里:
pub struct MultiName {
pub(crate) serialize: Name,
pub(crate) serialize_renamed: bool,
pub(crate) deserialize: Name,
pub(crate) deserialize_renamed: bool,
pub(crate) deserialize_aliases: BTreeSet<Name>,
}
序列化只有一个名字,反序列化有一个主名字加一组别名。两个 *_renamed 标志记录这个名字是不是用户用 rename 显式写的,给后面套 rename_all 时用。
算一个字段的名字分三步:
rename和alias。rename = "x"同时设序列化名和反序列化名;rename(serialize = "x")、rename(deserialize = "y")只设一边;alias = "z"只往别名集合里加。字段和变体上的rename(deserialize = ...)可以出现多次:第一个成为主名字(de_name.set_if_none),所有的都进别名集合。rename_all。Container::from_ast之后,ast.rs对每个字段调rename_by_rules:pub fn rename_by_rules(&mut self, rules: RenameAllRules) { if !self.name.serialize_renamed { self.name.serialize.value = rules.serialize.apply_to_field(&self.name.serialize.value); } if !self.name.deserialize_renamed { self.name.deserialize.value = rules .deserialize .apply_to_field(&self.name.deserialize.value); } self.name .deserialize_aliases .insert(self.name.deserialize.clone()); }显式
rename过的一边不再套规则。enum 的变体用容器的rename_all;变体里的字段先用变体自己的rename_all,没有才用容器的rename_all_fields(variant.attrs.rename_all_rules().or(attrs.rename_all_fields_rules()))。主名字进别名集合。 上面最后一句把最终的反序列化主名字也插进
deserialize_aliases。于是生成__Field的匹配代码时只需要遍历这一个集合,主名字和别名一视同仁。
src/bin/names.rs 把三种写法放在一起:
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Post {
// 显式 rename 优先于 rename_all
#[serde(rename(serialize = "Title", deserialize = "heading"))]
title: String,
// alias 只影响反序列化;序列化名仍由 rename_all 决定
#[serde(alias = "created")]
created_at: u32,
// 两次 rename(deserialize):第一个是名字,其余都当别名
#[serde(rename = "body")]
#[serde(rename(deserialize = "content"))]
text: String,
}
ser: {"Title":"t","createdAt":1,"body":"b"}
{"heading":"t","createdAt":1,"body":"b"}
-> Ok(Post { title: "t", created_at: 1, text: "b" })
{"heading":"t","created":2,"content":"c"}
-> Ok(Post { title: "t", created_at: 2, text: "c" })
{"Title":"t","createdAt":1,"body":"b"}
-> Err(Error("missing field `heading`", line: 1, column: 38))
{"heading":"t","created_at":1,"body":"b"}
-> Err(Error("missing field `createdAt`", line: 1, column: 41))
- 序列化名和反序列化名可以完全不同:写出
"Title",读回时只认"heading",拿自己的输出去读会报missing field。 - 套了
rename_all之后,Rust 里的原名created_at不再被认,它不在别名集合里。 - 缺字段的报错用的是反序列化主名字(
heading、createdAt),不是别名。
rename(deserialize) 写两次,以前是错误#1458(「Implements alias annotation and allow multiple deserialization renames.」)之前,反序列化名只能有一个。这个 PR 加了 alias,同时把字段和变体的 rename 改走新函数 get_multiple_renames:序列化一边仍然 at_most_one(),反序列化一边改成 de.get() 返回全部。它顺手删掉了测试 ui/duplicate-attribute/rename-rename-de.rs,那个用例写的正是 #[serde(rename = "x")] 加 #[serde(rename(deserialize = "y"))],原来期望的报错是 duplicate serde attribute rename``。首次发布在 1.0.85,发布说明:「Accept #[serde(alias = "...")] attribute on fields and variants which allows the same field or variant to be deserialized from any of multiple different names in the input」。容器上的 rename 至今还是 get_renames,两边都 at_most_one。
case.rs 的八条规则
RenameRule 除了 None 有八个变体,字符串和变体的对应表是 RENAME_RULES,from_str 按表逐个比较,大小写和连字符都要一字不差。
规则对字段和变体是两个函数,因为两者的输入约定不同:apply_to_variant 假定输入是 PascalCase,apply_to_field 假定输入是 snake_case。字段一侧的 lowercase 和 snake_case 直接返回原样,SCREAMING_SNAKE_CASE 就是 to_ascii_uppercase;变体一侧的 snake_case 是「在每个大写字母前面插 _,再全部转小写」:
SnakeCase => {
let mut snake = String::new();
for (i, ch) in variant.char_indices() {
if i > 0 && ch.is_uppercase() {
snake.push('_');
}
snake.push(ch.to_ascii_lowercase());
}
snake
}
SCREAMING_SNAKE_CASE、kebab-case、SCREAMING-KEBAB-CASE 都在 snake_case 的结果上再转大写或把 _ 换成 -。src/bin/rename_rules.rs 用一个宏给每条规则各生成一个 struct(字段 last_modified_at)和一个 enum(变体 LastModified、HTTPServer),打印序列化结果:
macro_rules! rule {
($out:ident, $rule:literal, $s:ident, $e:ident) => {
#[derive(Serialize)]
#[serde(rename_all = $rule)]
struct $s {
last_modified_at: u8,
}
#[derive(Serialize)]
#[serde(rename_all = $rule)]
enum $e {
LastModified,
HTTPServer,
}
println!(
"{:<22} {:<26} {}",
$rule,
serde_json::to_string(&$s { last_modified_at: 0 }).unwrap(),
serde_json::to_string(&$e::LastModified).unwrap(),
);
$out.push(($rule, serde_json::to_string(&$e::HTTPServer).unwrap()));
};
}
main 依次调用 rule!(http, "lowercase", S1, E1) 到 rule!(http, "SCREAMING-KEBAB-CASE", S8, E8),最后在 --- HTTPServer 一行之后打印收集起来的 HTTPServer 结果。输出的前八行:
lowercase {"last_modified_at":0} "lastmodified"
UPPERCASE {"LAST_MODIFIED_AT":0} "LASTMODIFIED"
PascalCase {"LastModifiedAt":0} "LastModified"
camelCase {"lastModifiedAt":0} "lastModified"
snake_case {"last_modified_at":0} "last_modified"
SCREAMING_SNAKE_CASE {"LAST_MODIFIED_AT":0} "LAST_MODIFIED"
kebab-case {"last-modified-at":0} "last-modified"
SCREAMING-KEBAB-CASE {"LAST-MODIFIED-AT":0} "LAST-MODIFIED"
对字段来说 UPPERCASE 和 SCREAMING_SNAKE_CASE 结果一样(下划线本来就在),对变体则不一样(LASTMODIFIED 和 LAST_MODIFIED)。
HTTPServer 套上 snake_case 是什么?同一个程序在 --- HTTPServer 之后打印变体 HTTPServer 的序列化结果。按上面 apply_to_variant 的 SnakeCase 分支,#[serde(rename_all = "snake_case")] 下它写出来是什么字符串?camelCase 下呢?
规则字符串写错时,报错把八个合法值全列出来(ParseError 的 Display)。rename_all = "camel" 这种只写一个值的形式会被拿去同时解析序列化和反序列化两条规则,第二次失败时有一个 one_name 判断跳过报错,所以只报一次(派生宏的流水线里 two_errors.rs 的第一条就是它)。
attr.rs 怎么解析
三个 from_ast 的骨架相同。以 Field::from_ast 为例,先为每个可能的属性建一个空槽:
let mut ser_name = Attr::none(cx, RENAME);
let mut de_name = Attr::none(cx, RENAME);
let mut de_aliases = VecAttr::none(cx, RENAME);
let mut skip_serializing = BoolAttr::none(cx, SKIP_SERIALIZING);
let mut skip_deserializing = BoolAttr::none(cx, SKIP_DESERIALIZING);
let mut skip_serializing_if = Attr::none(cx, SKIP_SERIALIZING_IF);
let mut default = Attr::none(cx, DEFAULT);
let mut serialize_with = Attr::none(cx, SERIALIZE_WITH);
// ...
然后遍历字段上的所有属性,跳过不是 serde 的、跳过空的 #[serde()],对每个 #[serde(...)] 调 syn 的 Attribute::parse_nested_meta:
if let Err(err) = attr.parse_nested_meta(|meta| {
if meta.path == RENAME {
// #[serde(rename = "foo")]
// #[serde(rename(serialize = "foo", deserialize = "bar"))]
let (ser, de) = get_multiple_renames(cx, &meta)?;
ser_name.set_opt(&meta.path, ser.as_ref().map(Name::from));
for de_value in de {
de_name.set_if_none(Name::from(&de_value));
de_aliases.insert(&meta.path, Name::from(&de_value));
}
} else if meta.path == ALIAS {
// ...
} else {
let path = meta.path.to_token_stream().to_string().replace(' ', "");
return Err(
meta.error(format_args!("unknown serde field attribute `{}`", path))
);
}
Ok(())
}) {
cx.syn_error(err);
}
parse_nested_meta 把括号里逗号分隔的每一项交给闭包一次,meta.path 是这一项的名字(rename),meta.input 是名字后面剩下的 token。闭包自己决定后面该是什么:meta.input.peek(Token![=]) 看是不是 = "...",meta.value()? 吃掉 = 拿到值,meta.parse_nested_meta(...) 递归解析 rename(serialize = ..., deserialize = ...) 这样的括号。
几个配套的小东西:
Symbol。RENAME、ALIAS这些常量是symbol.rs里的struct Symbol(&'static str)。它给syn::Path和syn::Ident实现了PartialEq<Symbol>(Path那个就是self.is_ident(word.0)),所以分支可以写成meta.path == RENAME;它的Display又是报错文字里的属性名。- 取值。
get_lit_str2把=后面解析成一个任意的syn::Expr,再检查它是不是字符串字面量;不是的话记一个expected serde rename attribute to be a string:rename = "..."`` 的错误,返回Ok(None),解析继续。default = "..."、with = "..."这类值再从字符串里解析成路径或类型。 serialize/deserialize分开写。get_ser_and_de处理rename、rename_all、bound共有的两种写法:= ...时两边用同一个值,(...)时里面只认serialize和deserialize,别的报malformed rename attribute, expectedrename(serialize = ..., deserialize = ...)``。
1.0.157 之前,attr.rs 用 get_serde_meta_items 先把整个 #[serde(...)] 解析成 Punctuated<Meta, Token![,]>,再对每个 Meta 做模式匹配;值必须能被解析成 syn::Meta 的语法。#2406(「Rewrite attribute parser using parse_nested_meta」)把它改成现在的回调:每一项的名字先出来,后面的 token 怎么解析由这一项自己决定。PR 描述只有一句「This is designed to be more amenable to unquoted values inside attributes -- #368.」,#368 是 2016 年的「Support default literals」(想写 #[serde(default = 0.1)])。这次重写随 syn 升到 2.x 一起发布在 1.0.157,发布说明是「Update syn dependency to 2.x」。到 1.0.229 为止,值仍然要求写成字符串字面量:get_lit_str2 先按任意表达式解析,再拒绝非字符串。
Attr、BoolAttr、VecAttr:查重
每个槽都是三种类型之一,它们只做一件事:记住值,并发现重复。
Attr<T> 存一个可选的值和设它时的 token(报错定位用),set 发现已经有值就报错:
fn set<A: ToTokens>(&mut self, obj: A, value: T) {
let tokens = obj.into_token_stream();
if self.value.is_some() {
let msg = format!("duplicate serde attribute `{}`", self.name);
self.cx.error_spanned_by(tokens, msg);
} else {
self.tokens = tokens;
self.value = Some(value);
}
}
报错的位置是第二次设值的 token,第一次的值保留。set_if_none 不报错,只在空的时候设;get_with_tokens 连同 token 一起取出来,给 decide_tag 这类需要在多个属性上同时报错的函数用(untagged 和 tag 一起写时,两个属性各标一条 enum cannot be both untagged and internally tagged)。
BoolAttr 是 Attr<()> 的包装,set_true 就是 set(obj, ()),所以 #[serde(skip_serializing, skip_serializing)] 一样报重复。
VecAttr<T> 收集多个值,插入时不检查;它记下第二个值的 token(first_dup_tokens),由调用方决定要一个还是要全部:at_most_one 在多于一个时报 duplicate,get 全部返回。rename(serialize = "a", serialize = "b") 走 at_most_one 报错,rename(deserialize = ...) 写多次走 get,就是上面冷知识里那个区别。
查重按槽,不按用户写的属性名。skip 展开成两个槽,with 展开成 serialize_with 和 deserialize_with 两个槽,所以组合起来也会撞(src/bin/hidden_dup.rs):
#[derive(Serialize)]
struct Post {
#[serde(with = "as_upper", serialize_with = "plain")]
title: String,
#[serde(skip, skip_serializing)]
cache: u64,
}
error: duplicate serde attribute `serialize_with`
--> src/bin/hidden_dup.rs:14:30
|
14 | #[serde(with = "as_upper", serialize_with = "plain")]
| ^^^^^^^^^^^^^^
error: duplicate serde attribute `skip_serializing`
--> src/bin/hidden_dup.rs:16:17
|
16 | #[serde(skip, skip_serializing)]
| ^^^^^^^^^^^^^^^^
报错里的名字是槽的名字(Attr::none(cx, SERIALIZE_WITH) 的那个 Symbol),不是用户写的 with。
拼错与写错
不认识的属性名走闭包最后的 else:return Err(meta.error(...))。这里用的是 return Err,不是 cx.error_spanned_by,效果不一样:parse_nested_meta 收到闭包的 Err 就停下,这个 #[serde(...)] 里后面的项不再解析,错误经 cx.syn_error(err) 记进 Ctxt,然后外层循环继续处理下一个 #[serde(...)]。src/bin/stop_at_unknown.rs:
#[derive(Serialize)]
struct Post {
#[serde(colour, rename = "a", rename = "b")]
title: String,
#[serde(rename = "c")]
#[serde(rename = "d")]
body: String,
}
error: unknown serde field attribute `colour`
--> src/bin/stop_at_unknown.rs:5:11
|
5 | #[serde(colour, rename = "a", rename = "b")]
| ^^^^^^
error: duplicate serde attribute `rename`
--> src/bin/stop_at_unknown.rs:8:11
|
8 | #[serde(rename = "d")]
| ^^^^^^
第 5 行同一个括号里重复的 rename 没有报,因为解析在 colour 就停了;第 7、8 行是两个独立的 #[serde],查重照常进行。值写错(不是字符串、rename(...) 里写了别的键)不中断,还会接着解析。各种报错的原文(src/bin/attr_errors.rs):
#[derive(Serialize)]
#[serde(deny_unknown_field)]
enum Status {
#[serde(rename = 1)]
Draft,
#[serde(rename(ser = "live"))]
Published,
#[serde(skip_serialising)]
Hidden,
}
error: unknown serde container attribute `deny_unknown_field`
--> src/bin/attr_errors.rs:4:9
|
4 | #[serde(deny_unknown_field)]
| ^^^^^^^^^^^^^^^^^^
error: expected serde rename attribute to be a string: `rename = "..."`
--> src/bin/attr_errors.rs:6:20
|
6 | #[serde(rename = 1)]
| ^
error: malformed rename attribute, expected `rename(serialize = ..., deserialize = ...)`
--> src/bin/attr_errors.rs:8:18
|
8 | #[serde(rename(ser = "live"))]
| ^^^
error: unknown serde variant attribute `skip_serialising`
--> src/bin/attr_errors.rs:10:11
|
10 | #[serde(skip_serialising)]
| ^^^^^^^^^^^^^^^^
error: could not compile `exp-serde-derive1` (bin "attr_errors") due to 4 previous errors
四条一次报完:它们分属四个 #[serde(...)],两条「不认识」各自只中断自己那一个列表,两条「值写错」根本不中断。这些文字和 test_suite/tests/ui/ 下的 .stderr 文件逐字对应,改动报错文字要同时改那些用例(见源码地图)。
动脑筋答案
src/bin/rename_rules.rs 的完整输出:
lowercase {"last_modified_at":0} "lastmodified"
UPPERCASE {"LAST_MODIFIED_AT":0} "LASTMODIFIED"
PascalCase {"LastModifiedAt":0} "LastModified"
camelCase {"lastModifiedAt":0} "lastModified"
snake_case {"last_modified_at":0} "last_modified"
SCREAMING_SNAKE_CASE {"LAST_MODIFIED_AT":0} "LAST_MODIFIED"
kebab-case {"last-modified-at":0} "last-modified"
SCREAMING-KEBAB-CASE {"LAST-MODIFIED-AT":0} "LAST-MODIFIED"
--- HTTPServer
lowercase "httpserver"
UPPERCASE "HTTPSERVER"
PascalCase "HTTPServer"
camelCase "hTTPServer"
snake_case "h_t_t_p_server"
SCREAMING_SNAKE_CASE "H_T_T_P_SERVER"
kebab-case "h-t-t-p-server"
SCREAMING-KEBAB-CASE "H-T-T-P-SERVER"
snake_case 下是 "h_t_t_p_server":H、T、T、P、S 都是大写,除了第一个,每个前面都插一个 _,再全部转小写。规则不认识缩写,只看「是不是大写字母」。camelCase 下是 "hTTPServer":CamelCase 分支只把第一个字符转小写(variant[..1].to_ascii_lowercase() + &variant[1..])。PascalCase 对变体原样返回。想要 http_server,变体要写成 HttpServer,或者给它单独 #[serde(rename = "http_server")],显式 rename 不再套规则。
小结
#[serde(...)]分三层,分别由attr::Container/attr::Variant/attr::Field的from_ast解析,各有一张认识的属性表,表外的名字报unknown serde ... attribute。- 名字存在
MultiName:一个序列化名、一个反序列化主名字、一组别名;rename_all只套在没有显式rename的一边,最终主名字也进别名集合。字段和变体的rename(deserialize)可以写多次(#1458,1.0.85)。 case.rs的规则对变体假定输入是PascalCase、对字段假定是snake_case;变体的snake_case在每个大写字母前插_,所以HTTPServer变成h_t_t_p_server。- 解析用 syn 的
parse_nested_meta,每一项一个回调,项后面的语法由这一项自己解析(#2406,1.0.157);属性值仍要求是字符串字面量。 Attr/BoolAttr第二次设值报duplicate serde attribute,VecAttr由调用方选at_most_one或get;查重按内部的槽,skip和skip_serializing、with和serialize_with会互相撞。- 不认识的属性让当前这个
#[serde(...)]停止解析,其余属性列表照常解析,所有错误一次报出。