web-dev-qa-db-fra.com

Comment désérialiser un champ optionnel avec des fonctions personnalisées à l'aide de Serde?

Je souhaite sérialiser et désérialiser un chrono::NaiveDate avec des fonctions personnalisées, mais le livre de Serde ne couvre pas cette fonctionnalité et les documents de code n'aident pas non plus.

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
extern crate chrono;

use chrono::NaiveDate;


mod date_serde {
    use chrono::NaiveDate;
    use serde::{self, Deserialize, Serializer, Deserializer};

    pub fn serialize<S>(date: &Option<NaiveDate>, s: S) -> Result<S::Ok, S::Error>
    where S: Serializer {
        if let Some(ref d) = *date {
            return s.serialize_str(&d.format("%Y-%m-%d").to_string())
        }
        s.serialize_none()
    }

    pub fn deserialize<'de, D>(deserializer: D)
        -> Result<Option<NaiveDate>, D::Error>
        where D: Deserializer<'de> {
        let s: Option<String> = Option::deserialize(deserializer)?;
        if let Some(s) = s {
            return Ok(Some(NaiveDate::parse_from_str(&s, "%Y-%m-%d").map_err(serde::de::Error::custom)?))
        }

        Ok(None)
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct Test {
    pub i: u64,
    #[serde(with = "date_serde")]
    pub date: Option<NaiveDate>,
}

fn main() {
    let mut test: Test = serde_json::from_str(r#"{"i": 3, "date": "2015-02-03"}"#).unwrap();
    assert_eq!(test.i, 3);
    assert_eq!(test.date, Some(NaiveDate::from_ymd(2015, 02, 03)));
    test = serde_json::from_str(r#"{"i": 5}"#).unwrap();
    assert_eq!(test.i, 5);
    assert_eq!(test.date, None);
}

Je le sais Option<chrono::NaiveDate> peut être facilement désérialisé par Serde parce que Chrono a le support de Serde, mais J'essaie d'apprendre Serde donc je veux l'implémenter moi-même. Lorsque j'exécute ce code, j'ai une erreur:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ErrorImpl { code: Message("missing field `date`"), line: 1, column: 8 }', /checkout/src/libcore/result.rs:859
note: Run with `Rust_BACKTRACE=1` for a backtrace.
20
Victor Polevoy

Le comportement par défaut de la désérialisation des structures consiste à affecter des champs avec leur valeur par défaut respective lorsqu'ils ne sont pas présents dans leur forme sérialisée. Notez que ceci est différent de l'attribut container #[serde(default)] , qui remplit les champs avec la valeur par défaut de la structure.

#[derive(Debug, PartialEq, Deserialize)]
pub struct Foo<'a> {
    x: Option<&'a str>,
}

let foo: Foo = serde_json::from_str("{}")?;
assert_eq!(foo, Foo { x: None });

Cependant, cette règle change lorsque nous utilisons une autre fonction de désérialisation ( #[serde(deserialize_with = "path")] ). Un champ de type Option ici n'indique plus au désérialiseur que le champ peut ne pas exister. Il suggère plutôt qu'il existe un champ avec un contenu vide ou nul possible (none en termes Serde). Dans serde_json Par exemple, Option<String> Est l'équivalent JavaScript de "null ou string" (null | string En notation TypeScript/Flow) . Ce code ci-dessous fonctionne très bien avec la définition et le désérialiseur de date donnés:

let test: Test = serde_json::from_str(r#"{"i": 5, "date": null}"#)?;
assert_eq!(test.i, 5);
assert_eq!(test.date, None);

Heureusement, le processus de désérialisation peut devenir plus permissif simplement en ajoutant l'attribut serde(default) (Option::default Donne None):

#[derive(Debug, Serialize, Deserialize)]
struct Test {
    pub i: u64,

    #[serde(default)]
    #[serde(with = "date_serde")]
    pub date: Option<NaiveDate>,
}

Aire de jeux

Voir également: