serde icon indicating copy to clipboard operation
serde copied to clipboard

Fallible defaults for non-present fields

Open junbl opened this issue 2 years ago • 3 comments

I want to be able to get configuration from a file, where individual fields can also be presented as environment variables.

The closest things to what I want are these attribute macros:

  1. deserialize_with: as far as I can tell, this isn't run at all if the field isn't present
  2. default: has to be infallible

So either a version of deserialize_with that is run even if the field isn't present at all (has some logic that can product a value in spite of this), or a version of default that returns a Result. If the field isn't present, and the default fails, that error will be propagated up to the deserialization caller.

Currently I'm using a default that panics if the values aren't found. This is fine for my current use case, but I'd like to be able to more gracefully handle that error.

junbl avatar Jul 15 '22 16:07 junbl

Hi there,

I've a somewhat similar problem when a field is missing and I want to use deserialize_with. When using serde_json, I'm accustomed to the fact that fields of type Option<T> can be omitted from the json string if they are null and deserialization will still work fine. If I provide a deserialize_with function, this behavior is disabled and parsing fails for fields that are omitted.

Here an example from the playground:

use serde::Deserialize;

#[derive(Deserialize)]
struct Foo {
    #[serde(deserialize_with = "deserialize_wrapper")]
    bar: Option<bool>,
}

fn deserialize_wrapper<'de, D>(
    d: D,
) -> Result<Option<bool>, D::Error>
where
    D: serde::de::Deserializer<'de>,
{
    Option::deserialize(d)
}

fn main() {
    let json = "{}";

    let f: Foo = serde_json::from_str(json).unwrap();

    assert_eq!(f.bar, None);
}

The wrapper function does nothing really, but parsing the empty json object panics in line 21 with the following error:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error("missing field `bar`", line: 1, column: 2)', src/main.rs:21:45

If you comment out the deserialize_with field attribute in line 5, parsing works as expected.

jofas avatar Aug 13 '22 10:08 jofas

I believe adding #[serde(default)] to the optional field will fix the issue. See this related issue: https://github.com/serde-rs/serde/issues/1728#issuecomment-579480080

jsscheller avatar Sep 05 '22 20:09 jsscheller

Yes, adding the #[serde(default)] tag solves my problem, thanks

jofas avatar Sep 06 '22 09:09 jofas

I want to be able to get configuration from a file, where individual fields can also be presented as environment variables.

The closest things to what I want are these attribute macros:

1. `deserialize_with`: as far as I can tell, this isn't run at all if the field isn't present

2. `default`: has to be infallible

So either a version of deserialize_with that is run even if the field isn't present at all (has some logic that can product a value in spite of this), or a version of default that returns a Result. If the field isn't present, and the default fails, that error will be propagated up to the deserialization caller.

Currently I'm using a default that panics if the values aren't found. This is fine for my current use case, but I'd like to be able to more gracefully handle that error.

I propose #[serde(try_default = function)], where function is a function with no arguments returning Result. All error types that serde::de::Error::custom accepts should be supported, i.e. everything that implements Display.

kangalio avatar Oct 31 '22 19:10 kangalio

I think I would prefer not to build something for try_default into serde_derive. But if someone wanted to put together a more fully featured derive macro as a separate library, plenty of people would be excited about that.

dtolnay avatar Jul 09 '23 21:07 dtolnay