つまり、デフォルト指定してても、いざデフォルトで取得してしまうと、意図した値がデフォルトにならない。
何言ってるんやって感じなので、順を追って書く。
default = "path"
属性とは
Field attributes · Serde https://serde.rs/field-attrs.html
Serdeでstruct
をJSONから生成するとき、つまりdeserializeするとき、オプショナルなキーなんかに初期値を設定したいとする。
その場合には、#[serde(default)]
という属性を書けばよい。
#[derive(Deserialize)]
struct Config {
#[serde(default)]
key: String,
}
ただこの指定では、その型の初期値しか指定できない。
String
なら空文字列bool
ならfalse
- 数値型は
0
- etc…
そこで、デフォルトをtrue
にしたいとか、独自の文字列にしたい場合には、#[serde(default = "path")]
と書きつつ、path
という関数を定義しておく。
serde::Deserializeで、default=trueしたい | Memory ice cubes https://leaysgur.github.io/posts/2024/03/13/213641
deserialize()
では呼ばれるけど
で、この#[serde(default = "path")]
による指定は、deserialize()
される時に呼ばれる。
let config: Config = serde_json::from_str(json).unwrap();
// or
let config = Config::deserialize(json).unwrap();
// or...
このときは、意図した通りにカスタムな初期値が指定されてる。
default()
では呼ばれない
これが問題のケース。
ついでに#[derive(Default)]
属性を付けて、default()
できるようにしてた場合。
let config = Config::default();
// or...
なんとこのときは、さっきのカスタムな初期値は指定されない。
バグでは・・・?って思ったけど、どうやら意図通りらしい。
Add #[derive(serde::Default)] that is aware of serde
default
annotations · Issue #2622 · serde-rs/serde https://github.com/serde-rs/serde/issues/2622
deserialize()
で呼ばれても、ネストの影響を受ける
つまりこういうこと。
use serde::Deserialize;
#[derive(Debug, Deserialize, Default)]
struct Config {
#[serde(default)]
k1: bool,
#[serde(default = "default_true")]
k2: bool,
#[serde(default)]
nested: Nested,
}
#[derive(Debug, Deserialize, Default)]
struct Nested {
#[serde(default)]
k1: bool,
#[serde(default = "default_true")]
k2: bool,
}
fn default_true() -> bool {
true
}
fn main() {
let c = Config::deserialize(serde_json::json!({})).unwrap();
println!("{c:?}");
let c = Config::default();
println!("{c:?}");
}
これを実行すると、こうなる。
Config { k1: false, k2: true, nested: Nested { k1: false, k2: false } }
Config { k1: false, k2: false, nested: Nested { k1: false, k2: false } }
前者はc.nested.k2
がfalse
になってしまってて、後者はc.k2
ですらfalse
になってしまってる。
どうする
先のIssueでは、これを解決するためのクレートを作ってる人がいた。
BrynCooke/serde-derive-default https://github.com/BrynCooke/serde-derive-default
これで済ませられるならそれでいい気もするけど、正解はわかってない。
自分でDefault
を実装するのが、正攻法なんだろうか。