🦀

Serdeでfalseのフィールドを無視する

に公開

Using derive - SerdeのサンプルコードにnameというOption<String>のフィールドを追加した次のコードからはじめましょう。

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Point {
    x: i32,
    y: i32,
    name: Option<String>,
}

fn main() {
    let point = Point { x: 1, y: 2, name: None };

    let serialized = serde_json::to_string(&point).unwrap();
    println!("serialized = {}", serialized);

    let deserialized: Point = serde_json::from_str(&serialized).unwrap();
    println!("deserialized = {:?}", deserialized);
}

このコードを実行すると

> cargo run
...
serialized = {"x":0,"y":0,"name":null}
deserialized = Point { x: 0, y: 0, name: None }

nameNoneなので保存したくないですよね。

Serdeではskip_serialize_ifを使うと、特定の条件のときにフィールドを無視することができます。

    #[serde(skip_serializing_if = "Option::is_none")]
    name: Option<String>,

こうするとname: nullが出力されなくなりました。

> cargo run
...
serialized = {"x":1,"y":2}
deserialized = Point { x: 1, y: 2, name: None }

さらに、boolのフィールドを追加します。

#[derive(Serialize, Deserialize, Debug)]
struct Point {
    x: i32,
    y: i32,

    #[serde(skip_serializing_if = "Option::is_none")]
    name: Option<String>,

    hidden: bool,
}

実行してみましょう。

...
serialized = {"x":1,"y":2,"hidden":false}
deserialized = Point { x: 1, y: 2, name: None, hidden: false }

やはり、falseは無視したいですね。skip_serializing_ifには関数を指定する必要がありますので、std::ops::Not::notを使えばfalseのときtrueになってskipされそうです。

use std::ops::Not;

...

    #[serde(skip_serializing_if = "Not::not")]
    hidden: bool,

実行するとserializeはうまくいきましたが、deserializeでエラーになります。 [1]

> cargo run
...
serialized = {"x":1,"y":2}

thread 'main' panicked at src\main.rs:23:65:
called `Result::unwrap()` on an `Err` value: Error("missing field `hidden`", line: 1, column: 13)

これを解決するためにはdefaultを設定する必要があります。

    #[serde(default, skip_serializing_if = "Not::not")]
    hidden: bool,

このserde(default)PointDefaultをderiveしていても関係なく必要です。

defaultはどっちのdefault?

PointDefaulthiddentrueになっているとき、serde(default)true / falseのどちらだろう? PointDefaultを実装しているかは無関係なことからboolのdefaultのfalseになるんじゃないかなと予想するけど

#[derive(Serialize, Deserialize, Debug)]
struct Point {
    x: i32,
    y: i32,

    #[serde(skip_serializing_if = "Option::is_none")]
    name: Option<String>,

    #[serde(default, skip_serializing_if = "Not::not")]
    hidden: bool,
}

impl Default for Point {
    fn default() -> Self {
        Point { x: 0, y: 0, name: None, hidden: true }
    }
}

実行すると、

> cargo run
...
serialized = {"x":1,"y":2}
deserialized = Point { x: 1, y: 2, name: None, hidden: false }

予想通りですね。

Donts

boolではなくOption<bool>

ぼくも上の書き方を知るまではそうしていました。でも、これだとNone, Some(false), Some(true)の3つ値を考えないといけなくなるんですよね。

使うときにpoint.hidden.unwrap_or(false)とか書かなくてはいけなくなって大変。

defaultをtrueにする

boolのフィールドのデフォルトをtrueにするというのは技術的にはできますが、お勧めしません。

プログラムのバージョンアップとともに、フィールドは追加されたり削除されたりしていきます。しかし、一度、serializeされたデータはどこかに残っているものです。未来のプログラムが問題なく動作するためには、フィールドの型のデフォルト値を使うのがもっとも自然で安全です。

例えばhiddenのデフォルトをtrueにしたいのであれば、フィールド名を反対の意味のvisibleにすればデフォルトはfalseになります。[2]

最終的なコード

Cargo.toml

[package]
name = "serde_skip_false"
version = "0.1.0"
edition = "2024"

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"

src/main.rs

use std::ops::Not;

use serde::{Serialize, Deserialize};

#[derive(Default, Serialize, Deserialize, Debug)]
struct Point {
    x: i32,
    y: i32,

    #[serde(skip_serializing_if = "Option::is_none")]
    name: Option<String>,

    #[serde(default, skip_serializing_if = "Not::not")]
    hidden: bool,
}

fn main() {
    let point = Point { x: 1, y: 2, name: None, hidden: false };
    // let point = Point::default();

    let serialized = serde_json::to_string(&point).unwrap();
    println!("serialized = {}", serialized);

    let deserialized: Point = serde_json::from_str(&serialized).unwrap();
    println!("deserialized = {:?}", deserialized);
}
脚注
  1. nameに対してはエラーがでないのは、Optionはdeserializeでフィールドの値がないときはNoneになるということになっているのでしょう。 ↩︎

  2. 他のシステム由来のJSONを扱うときは… ↩︎

GitHubで編集を提案

Discussion