Serde + TOML + deserialize_with on Options

Sertde + TOML handles deserialization of missing Option fields by producing None:

    #[test]
    fn toml_u32() {
        #[derive(Deserialize, Debug)]
        struct X { a: Option<u32> }
        assert_eq!(parse::<X>("a = 10").a, Some(10));
        assert_eq!(parse::<X>("      ").a, None    );
    }

Where parse is this helper function:

fn parse<'d, D: Deserialize<'d>>(input: &'d str) -> D {
    toml::from_str(input).unwrap()
}

Can this give-me-None-if-field-missing behaviour be implemented when using [serde(dezerialize_with = ...)]?

My attempts give me "missing field" errors:


    #[test]
    fn toml_u32_deserialize_with() {
        #[derive(Deserialize, Debug)]
        struct X {
            #[serde(deserialize_with = "bbb")]
            a: Option<u32>
        }
        assert_eq!(parse::<X>("a = 10").a, Some(10));
        assert_eq!(parse::<X>("      ").a, None    );

        fn bbb<'d, D: Deserializer<'d>>(deserializer: D) -> Result<Option<u32>, D::Error> {
            Ok(Option::<u32>::deserialize(deserializer)?)
        }
    }

gives the error

panicked at 'called `Result::unwrap()` on an `Err` value: Error { inner: ErrorInner { kind: Custom, line: None, col: 0, at: None, message: "missing field `a`", key: [] } }'

on the second assert_eq!.

playground

Background

I'm trying to parse optional fields whose types are physical quantities from uom.

This works if the quantity is expressed as a number without units

    #[test]
    fn toml_time_without_units() {
        #[derive(Deserialize, Debug)]
        struct X { a: Option<Time> }
        assert_eq!(parse::<X>("a = 2").a, Some(ps(2.)));
        assert_eq!(parse::<X>("     ").a, None        );
    }

with the base unit of the quantity being used implicitly, but this defeats half of the point of uom: ensuring that units are explicit! uom provides parsers for numbers-with-units:

        let t: Time = "2 ps".parse()?;
        assert_eq!(t, ps(2.));

        let t: Time = "2 ns".parse()?; // `ns` instead of `ps`
        assert_eq!(t, ps(2000.));

so I want these to be used in the deserialization process.

In other words, I want to pass tests like these:


    #[test]
    fn toml_time_with_units() {
        #[derive(Deserialize, Debug)]
        struct X {
            #[serde(deserialize_with = "bbb")]
            a: Option<Time>
        }
        assert_eq!(parse::<X>(r#"a = "2 ps""#).a, Some(ps(2.   )));
        assert_eq!(parse::<X>(r#"a = "2 ns""#).a, Some(ps(2000.)));
        assert_eq!(parse::<X>(r#"          "#).a, None           );
    }

I haven't added the uom examples to the playground, as uom is not available there.

This stackoverflow answer suggests a solution: in short, add #[serde(default)] to the field.

This seems to sovle my problem.

(playground)