Rustの練習でツールを作っている。その中で、APIレスポンスのJSONを構造体にデシリアライズするときに悩んだのでメモ。
主に悩んだのは、デシリアライズ先の構造体のフィールドが Vec<構造体> とか Option<構造体> のとき。特に Option<構造体> にデシリアライズしたいデータがJSONに存在しないときにNoneを割り当てるにはどうすればいいかという点。

この記事のコードはここにある
デシリアライズのサンプルを person1 から person6 まで作っているが、 person3 は一部動かないので、この記事では書かない。
自分がやりたかったこととしては person2 で完了している。 person5person6 の方法でもいいかもという感じ。

この記事ではシリアライズは一切考えない。

やりたいこと

以下のようなJSONがある。イメージとしては、なにかのサークルのメンバーリストで、年齢や会社名などが記録されたもの。会社名(company)は所属がない場合は null になったり、フィールド自体がなかったりするものとする。

{
    "name": "Alice",
    "age": 42,
    "is_active": true,
    "mails": [
        "alice@example.com",
        "wonderland@example.com"
    ],
    "company": "ABC technologies"
}

これを以下のような構造体にデシリアライズしたい。 NameAge などはフィールド1つだけを持っている構造体。

struct Person {
    name: Name,
    age: Age,
    is_active: IsActive,
    mails: Vec<MailAddress>,
    company: Option<Company>,
}

いくつか例を書く。

  • 普通のもの(JSON1)
    company に値が入っている。このときは Some(...) にしたい。
    {
        "name": "Alice",
        "age": 42,
        "is_active": true,
        "mails": [
            "alice@example.com",
            "wonderland@example.com"
        ],
        "company": "ABC technologies"
    }
    
    • デシリアライズ後
      Person {
          name: Name {
              value: "Alice".to_string(),
          },
          age: Age { value: 42 },
          is_active: IsActive { value: true },
          mails: vec![
              MailAddress {
                  value: "alice@example.com".to_string(),
              },
              MailAddress {
                  value: "wonderland@example.com".to_string(),
              },
          ],
          company: Some(Company {
              value: "ABC technologies".to_string(),
          }),
      }
      
  • company がnullのもの(JSON2)
    このときは None として扱いたい。
    {
        "name": "Bob",
        "age": 43,
        "is_active": false,
        "mails": [
            "bob@example.com"
        ],
        "company": null
    }
    
    • デシリアライズ後
      Person {
          name: Name {
              value: "Bob".to_string(),
          },
          age: Age { value: 43 },
          is_active: IsActive { value: false },
          mails: vec![MailAddress {
              value: "bob@example.com".to_string(),
          }],
          company: None,
      }
      
  • company フィールド自体ないもの(JSON3)
    この場合も None として扱いたい。
    {
        "name": "Carol",
        "age": 44,
        "is_active": true,
        "mails": []
    }
    
    • デシリアライズ後
      Person {
          name: Name {
              value: "Carol".to_string(),
          },
          age: Age { value: 44 },
          is_active: IsActive { value: true },
          mails: vec![],
          company: None,
      }
      
      

環境

  • Rust
    1.54.0
  • serde
    1.0
  • serde_json
    1.0
  • serde_with
    1.10.0

Cargo.toml

この記事のコードは [dependencies] 以降をこのようにした状態で動かす。

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

[dependencies.serde_with]
version = "1.10.0"

serdeserde_json は全体で使う。
serde_with は最後の方で使うだけ(person6)。

単純な例(person1)

まず、 Person 構造体のフィールドが構造体ではなく、単なる文字列型などの場合を考えてみる。
基本的な文字列型や整数型などは #[derive(Deserialize)] と書いておけば、serde_jsonでよろしくやってくれる。OptionやVecも同様。例がドキュメントにある。
これを使うと以下のようになる。

なおderiveで Debug と書いてあるのはprintlnしたいため、 PartialEqEq はテストでassert_eqしたいためなので、これらはデシリアライズとは関係ない。

person1.rs

use serde::Deserialize;

#[derive(Deserialize, Debug, PartialEq, Eq)]
pub struct Person {
    name: String,
    age: u8,
    is_active: bool,
    mails: Vec<String>,
    company: Option<String>,
}

pub fn json_to_person(json: &str) -> Result<Person, serde_json::Error> {
    serde_json::from_str(json)
}

main.rs

use sample_deserialize_with_serde::jsons::{JSON1, JSON2, JSON3};

use sample_deserialize_with_serde::person1;

fn main() {
    // person1
    println!("[person1]");
    let p1_1: person1::Person = person1::json_to_person(JSON1).unwrap();
    let p1_2: person1::Person = person1::json_to_person(JSON2).unwrap();
    let p1_3: person1::Person = person1::json_to_person(JSON3).unwrap();

    println!("p1_1 = {:?}", p1_1);
    println!("p1_2 = {:?}", p1_2);
    println!("p1_3 = {:?}", p1_3);
}

cargo run の結果

[person1]
p1_1 = Person { name: "Alice", age: 42, is_active: true, mails: ["alice@example.com", "wonderland@example.com"], company: Some("ABC technologies") }
p1_2 = Person { name: "Bob", age: 43, is_active: false, mails: ["bob@example.com"], company: None }
p1_3 = Person { name: "Carol", age: 44, is_active: true, mails: [], company: None }

カスタムな型にマッピングする(person2)

上の例ではJSONのフィールドの値をStringやu8など単純な型にした。

こうするよりもカスタムな型にマッピングする方があとあと扱いやすいことがある(特にDDDでやっているとき)。
たとえば何かの処理で name(String型) のVecを作る場合に、誤って mails(Vec<String>型) の要素が紛れ込んでしまったとする。このとき、どちらもStringなのでミスに気付けない。違う型にすればコンパイルエラーになるのですぐ気付くことができる。

そこで

struct Person {
    name: String,
    age: u8,
    is_active: bool,
    mails: Vec<String>,
    company: Option<String>,
}

としていたのを

struct Person {
    name: Name,
    age: Age,
    is_active: IsActive,
    mails: Vec<MailAddress>,
    company: Option<Company>,
}

にする。つまりフィールドを構造体にした。
(たぶん実際にはここまでカスタムな型にする必要はないはず。たとえば is_active はboolのままで何も困らないと思う。他のフィールドも場合によりけり。今回は練習のためにこうやっている。)

さて Name などは適当な構造体にするが、JSONのどのフィールドを、構造体のどのフィールドにどうマッピングするかは自分で書く必要がある。
これは Name を次のように定義したときを考えればわかる。

struct Name {
    first_name: String,
    last_name: String,
}

この場合、JSONの "name": "Alice" の情報を Name のどのフィールドに入れるのかは、Rust側ではわからない。
今回はこう定義せずに

struct Name {
    value: String,
}

とするが、このようにフィールドを1つだけにしても、自分でデシリアライズ処理を書かないといけないっぽい。

このようにデシリアライザを自分で書くときは以下を参考にする。

person2.rs

フィールドの構造体一つ一つに対し、 VisitorDeserialize を実装する。

Age.value の型はu8なのに visit_u64 しか実装していないのが変に見えるが、これは visit_u8 のデフォルト実装が visit_u64 だかららしい。 visit_u8 だけを実装すると実行時エラーになる。
これはvisit_u8のドキュメントに書いてある。

Vec<MailAddress>Option<Company> については、MailAddressやCompanyに対する処理を書けばいいだけで、他に特別な処理を書く必要はないようだ。これは、MailAddressやCompany自体は構造体だが、それがVecやOptionにくるまれているので、serdeの機能で自動的に対処してくれるということなのだろう(…たぶん)。

use serde::Deserialize;
use serde::de::{self, Visitor};
use std::fmt;

#[derive(Deserialize, Debug, PartialEq, Eq)]
pub struct Person {
    name: Name,
    age: Age,
    is_active: IsActive,
    mails: Vec<MailAddress>,
    company: Option<Company>,
}

#[derive(Debug, PartialEq, Eq)]
struct Name {
    value: String,
}

#[derive(Debug, PartialEq, Eq)]
struct Age {
    value: u8,
}

#[derive(Debug, PartialEq, Eq)]
struct IsActive {
    value: bool,
}

#[derive(Debug, PartialEq, Eq)]
struct MailAddress {
    value: String,
}

#[derive(Debug, PartialEq, Eq)]
struct Company {
    value: String,
}

struct NameVisitor;
impl<'de> Visitor<'de> for NameVisitor {
    type Value = Name;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("String for Name")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(Name {
            value: v.to_string(),
        })
    }
}
impl<'de> Deserialize<'de> for Name {
    fn deserialize<D>(deserializer: D) -> Result<Name, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_str(NameVisitor)
    }
}

struct AgeVisitor;
impl<'de> Visitor<'de> for AgeVisitor {
    type Value = Age;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("Integer for Age")
    }

    fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(Age { value: v as u8 })
    }
}
impl<'de> Deserialize<'de> for Age {
    fn deserialize<D>(deserializer: D) -> Result<Age, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_u64(AgeVisitor)
    }
}

struct IsActiveVisitor;
impl<'de> Visitor<'de> for IsActiveVisitor {
    type Value = IsActive;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("Bool for IsActive")
    }

    fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(IsActive { value: v })
    }
}
impl<'de> Deserialize<'de> for IsActive {
    fn deserialize<D>(deserializer: D) -> Result<IsActive, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_bool(IsActiveVisitor)
    }
}

struct MailAddressVisitor;
impl<'de> Visitor<'de> for MailAddressVisitor {
    type Value = MailAddress;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("String for MailAddress")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(MailAddress {
            value: v.to_string(),
        })
    }
}
impl<'de> Deserialize<'de> for MailAddress {
    fn deserialize<D>(deserializer: D) -> Result<MailAddress, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_str(MailAddressVisitor)
    }
}

struct CompanyVisitor;
impl<'de> Visitor<'de> for CompanyVisitor {
    type Value = Company;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("String for Company")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(Company {
            value: v.to_string(),
        })
    }
}
impl<'de> Deserialize<'de> for Company {
    fn deserialize<D>(deserializer: D) -> Result<Company, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_str(CompanyVisitor)
    }
}

pub fn json_to_person(json: &str) -> Result<Person, serde_json::Error> {
    serde_json::from_str(json)
}

main.rs

use sample_deserialize_with_serde::jsons::{JSON1, JSON2, JSON3};

use sample_deserialize_with_serde::person2;

fn main() {
    // person2
    println!("\n[person2]");
    let p2_1: person2::Person = person2::json_to_person(JSON1).unwrap();
    let p2_2: person2::Person = person2::json_to_person(JSON2).unwrap();
    let p2_3: person2::Person = person2::json_to_person(JSON3).unwrap();

    println!("p2_1 = {:?}", p2_1);
    println!("p2_2 = {:?}", p2_2);
    println!("p2_3 = {:?}", p2_3);
}

cargo run の結果

[person2]
p2_1 = Person { name: Name { value: "Alice" }, age: Age { value: 42 }, is_active: IsActive { value: true }, mails: [MailAddress { value: "alice@example.com" }, MailAddress { value: "wonderland@example.com" }], company: Some(Company { value: "ABC technologies" }) }
p2_2 = Person { name: Name { value: "Bob" }, age: Age { value: 43 }, is_active: IsActive { value: false }, mails: [MailAddress { value: "bob@example.com" }], company: None }
p2_3 = Person { name: Name { value: "Carol" }, age: Age { value: 44 }, is_active: IsActive { value: true }, mails: [], company: None }

タプル構造体を使う(person4)

上(person2)で、「JSONのどのフィールドを、構造体のどのフィールドにマッピングするかはRust側にはわからないので、自分で処理を書く必要がある」のように書いた。
要素数1つのタプル構造体ならRust側にもどうマッピングすればいいかわかるのではと思ったので、やってみたら一応うまくいったっぽい。今回のように単純な構造体にマッピングするならこれでいいと思う。

person4.rs

use serde::Deserialize;

#[derive(Deserialize, Debug, PartialEq, Eq)]
pub struct Person {
    name: NameTuple,
    age: AgeTuple,
    is_active: IsActiveTuple,
    mails: Vec<MailAddressTuple>,
    company: Option<CompanyTuple>,
}

#[derive(Deserialize, Debug, PartialEq, Eq)]
struct NameTuple(String);

#[derive(Deserialize, Debug, PartialEq, Eq)]
struct AgeTuple(u8);

#[derive(Deserialize, Debug, PartialEq, Eq)]
struct IsActiveTuple(bool);

#[derive(Deserialize, Debug, PartialEq, Eq)]
struct MailAddressTuple(String);

#[derive(Deserialize, Debug, PartialEq, Eq)]
struct CompanyTuple(String);

pub fn json_to_person(json: &str) -> Result<Person, serde_json::Error> {
    serde_json::from_str(json)
}

main.rs

use sample_deserialize_with_serde::jsons::{JSON1, JSON2, JSON3};

use sample_deserialize_with_serde::person4;

fn main() {
    // person4
    println!("\n[person4]");
    let p4_1: person4::Person = person4::json_to_person(JSON1).unwrap();
    let p4_2: person4::Person = person4::json_to_person(JSON2).unwrap();
    let p4_3: person4::Person = person4::json_to_person(JSON3).unwrap();

    println!("p4_1 = {:?}", p4_1);
    println!("p4_2 = {:?}", p4_2);
    println!("p4_3 = {:?}", p4_3);
}

cargo run の結果

[person4]
p4_1 = Person { name: NameTuple("Alice"), age: AgeTuple(42), is_active: IsActiveTuple(true), mails: [MailAddressTuple("alice@example.com"), MailAddressTuple("wonderland@example.com")], company: Some(CompanyTuple("ABC technologies")) }
p4_2 = Person { name: NameTuple("Bob"), age: AgeTuple(43), is_active: IsActiveTuple(false), mails: [MailAddressTuple("bob@example.com")], company: None }
p4_3 = Person { name: NameTuple("Carol"), age: AgeTuple(44), is_active: IsActiveTuple(true), mails: [], company: None }

deserialize_with アトリビュートを使う(person5)

今回、serdeを使ったJSONデシリアライズの例を調べていたら、このアトリビュートを使っている人が多かった。こっちの方が一般的なのだろうか…?

person2の例だと VisitorDeserialize を自分で実装しなければならず、記述量が多かった。
deserialize_with を使うとちょっと少なくできる。ただし Option<T>Vec<T> などは工夫する必要があるようだ。

person5.rs

deserialize_with の使い方としては #[serde(deserialize_with="デシリアライズに使う関数の名前")] をフィールドにつけて、そのデシリアライズ用関数を書く。

Vec<MailAddress> については、 deserialize_vec_mail_address でVecを外し、1つ1つの要素については deserialize_mail_address を使ってデシリアライズするという方法を取っている。 Option<Company> も同様。
これは以下を参考にした(つもりだが、ちゃんと理解できたかわからない)。

あとこのアトリビュートを使うと、JSONに company フィールドがないときに実行時エラーが起きるようだった。そのため #[serde(default = "default_option_company")] というアトリビュートもつけ、 default_option_company() という関数でNoneを返している。

use serde::de::Deserializer;
use serde::Deserialize;

#[derive(Deserialize, Debug, PartialEq, Eq)]
pub struct Person {
    #[serde(deserialize_with = "deserialize_name")]
    name: Name,
    #[serde(deserialize_with = "deserialize_age")]
    age: Age,
    #[serde(deserialize_with = "deserialize_is_active")]
    is_active: IsActive,
    #[serde(deserialize_with = "deserialize_vec_mail_address")]
    mails: Vec<MailAddress>,
    #[serde(deserialize_with = "deserialize_option_company")]
    #[serde(default = "default_option_company")]
    company: Option<Company>,
}

#[derive(Debug, PartialEq, Eq)]
struct Name {
    value: String,
}
#[derive(Debug, PartialEq, Eq)]
struct Age {
    value: u8,
}
#[derive(Debug, PartialEq, Eq)]
struct IsActive {
    value: bool,
}

#[derive(Debug, PartialEq, Eq)]
struct MailAddress {
    value: String,
}

#[derive(Debug, PartialEq, Eq)]
struct Company {
    value: String,
}

fn deserialize_name<'de, D>(deserializer: D) -> Result<Name, D::Error>
where
    D: Deserializer<'de>,
{
    let v = String::deserialize(deserializer)?;
    Ok(Name { value: v })
}
fn deserialize_age<'de, D>(deserializer: D) -> Result<Age, D::Error>
where
    D: Deserializer<'de>,
{
    let v = u8::deserialize(deserializer)?;
    Ok(Age { value: v })
}
fn deserialize_is_active<'de, D>(deserializer: D) -> Result<IsActive, D::Error>
where
    D: Deserializer<'de>,
{
    let v = bool::deserialize(deserializer)?;
    Ok(IsActive { value: v })
}

fn deserialize_mail_address<'de, D>(deserializer: D) -> Result<MailAddress, D::Error>
where
    D: Deserializer<'de>,
{
    let v = String::deserialize(deserializer)?;
    Ok(MailAddress { value: v })
}

fn deserialize_vec_mail_address<'de, D>(deserializer: D) -> Result<Vec<MailAddress>, D::Error>
where
    D: Deserializer<'de>,
{
    #[derive(Deserialize)]
    struct Wrapper(#[serde(deserialize_with = "deserialize_mail_address")] MailAddress);

    let v = Vec::deserialize(deserializer)?;
    Ok(v.into_iter().map(|Wrapper(a)| a).collect())
}

fn deserialize_company<'de, D>(deserializer: D) -> Result<Company, D::Error>
where
    D: Deserializer<'de>,
{
    let v = String::deserialize(deserializer)?;
    Ok(Company { value: v })
}

fn deserialize_option_company<'de, D>(deserializer: D) -> Result<Option<Company>, D::Error>
where
    D: Deserializer<'de>,
{
    #[derive(Deserialize)]
    struct Wrapper(#[serde(deserialize_with = "deserialize_company")] Company);

    let v = Option::deserialize(deserializer)?;
    Ok(v.map(|Wrapper(a)| a))
}
fn default_option_company() -> Option<Company> {
    None
}

pub fn json_to_person(json: &str) -> Result<Person, serde_json::Error> {
    serde_json::from_str(json)
}

main.rs

use sample_deserialize_with_serde::jsons::{JSON1, JSON2, JSON3};

use sample_deserialize_with_serde::person5;

fn main() {
    // person5
    println!("\n[person5]");
    let p5_1: person5::Person = person5::json_to_person(JSON1).unwrap();
    let p5_2: person5::Person = person5::json_to_person(JSON2).unwrap();
    let p5_3: person5::Person = person5::json_to_person(JSON3).unwrap();

    println!("p5_1 = {:?}", p5_1);
    println!("p5_2 = {:?}", p5_2);
    println!("p5_3 = {:?}", p5_3);
}

cargo run の結果

person2 のときと同じ。

[person5]
p5_1 = Person { name: Name { value: "Alice" }, age: Age { value: 42 }, is_active: IsActive { value: true }, mails: [MailAddress { value: "alice@example.com" }, MailAddress { value: "wonderland@example.com" }], company: Some(Company { value: "ABC technologies" }) }
p5_2 = Person { name: Name { value: "Bob" }, age: Age { value: 43 }, is_active: IsActive { value: false }, mails: [MailAddress { value: "bob@example.com" }], company: None }
p5_3 = Person { name: Name { value: "Carol" }, age: Age { value: 44 }, is_active: IsActive { value: true }, mails: [], company: None }

serde_withを使う(person6)

person5では Vec<T>Option<T> の処理が面倒だった。serde_withを使うと少し楽になる。

person6.rs

mailscompanyserde_as というのを使っている。これがserde_withのアトリビュート。
これをつけておけば、デシリアライズ先の構造体(いまの場合は MailAddressCompany)がFromStrを実装してあればそれを使ってデシリアライズしてくれる、ということらしい。なのでFromStrを実装する必要はある。

なお以下の例では Name でもdeserialize_asを使っている。これは単なる練習であり、person5のときのようにdeserialize_withを使っても変わらない。できれば AgeIsActive でも使ってみたかったが、方法がよくわからなかった。

use serde::de::Deserializer;
use serde::Deserialize;
use serde_with::{serde_as, DisplayFromStr};
use std::str::FromStr;
use std::string::ParseError;

#[serde_as]
#[derive(Deserialize, Debug, PartialEq, Eq)]
pub struct Person {
    #[serde_as(deserialize_as = "DisplayFromStr")]
    name: Name,
    #[serde(deserialize_with = "deserialize_age")]
    age: Age,
    #[serde(deserialize_with = "deserialize_is_active")]
    is_active: IsActive,
    #[serde_as(deserialize_as = "Vec<DisplayFromStr>")]
    mails: Vec<MailAddress>,
    #[serde_as(deserialize_as = "Option<DisplayFromStr>")]
    #[serde(default = "default_option_company")]
    company: Option<Company>,
}

#[derive(Debug, PartialEq, Eq)]
struct Name {
    value: String,
}

impl FromStr for Name {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Name {
            value: s.to_string(),
        })
    }
}

#[derive(Debug, PartialEq, Eq)]
struct Age {
    value: u8,
}

#[derive(Debug, PartialEq, Eq)]
struct IsActive {
    value: bool,
}

#[derive(Debug, PartialEq, Eq)]
struct MailAddress {
    value: String,
}

#[derive(Debug, PartialEq, Eq)]
struct Company {
    value: String,
}

fn deserialize_age<'de, D>(deserializer: D) -> Result<Age, D::Error>
where
    D: Deserializer<'de>,
{
    let v = u8::deserialize(deserializer)?;
    Ok(Age { value: v })
}
fn deserialize_is_active<'de, D>(deserializer: D) -> Result<IsActive, D::Error>
where
    D: Deserializer<'de>,
{
    let v = bool::deserialize(deserializer)?;
    Ok(IsActive { value: v })
}
fn default_option_company() -> Option<Company> {
    None
}

impl FromStr for MailAddress {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(MailAddress {
            value: s.to_string(),
        })
    }
}

impl FromStr for Company {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Company {
            value: s.to_string(),
        })
    }
}

pub fn json_to_person(json: &str) -> Result<Person, serde_json::Error> {
    serde_json::from_str(json)
}

main.rs

use sample_deserialize_with_serde::jsons::{JSON1, JSON2, JSON3};

use sample_deserialize_with_serde::person6;

fn main() {
    // person6
    println!("\n[person6]");
    let p6_1: person6::Person = person6::json_to_person(JSON1).unwrap();
    let p6_2: person6::Person = person6::json_to_person(JSON2).unwrap();
    let p6_3: person6::Person = person6::json_to_person(JSON3).unwrap();

    println!("p6_1 = {:?}", p6_1);
    println!("p6_2 = {:?}", p6_2);
    println!("p6_3 = {:?}", p6_3);
}

cargo run の結果

person2のときと同じ。

[person6]
p6_1 = Person { name: Name { value: "Alice" }, age: Age { value: 42 }, is_active: IsActive { value: true }, mails: [MailAddress { value: "alice@example.com" }, MailAddress { value: "wonderland@example.com" }], company: Some(Company { value: "ABC technologies" }) }
p6_2 = Person { name: Name { value: "Bob" }, age: Age { value: 43 }, is_active: IsActive { value: false }, mails: [MailAddress { value: "bob@example.com" }], company: None }
p6_3 = Person { name: Name { value: "Carol" }, age: Age { value: 44 }, is_active: IsActive { value: true }, mails: [], company: None }

おわりに

むずい。書き方はこれでいいのだと思うが、なぜそう書けるのかがわかってない、ということがこれを書いていてよくわかった。デシリアライズするときにserde内部でどんな処理が行われているのか、調べる必要がある。

参考

本文中に挙げたもの以外を書く。

変更履歴

  • 2021/09/29
    文章表現の一部を修正。