4

I'm parsing data into:

struct Data {
    field1: Option<f32>,
    field2: Option<u64>,
    // more ...
}

The problem is that my input data format formats what would be a None in Rust as "n/a".

How do tell Serde that an Option<T> should be None for the specific string n/a, as opposed to an error? We can assume that this doesn't apply to a String.

This isn't the same question as How to deserialize "NaN" as `nan` with serde_json? because that's creating an f32 from a special value whereas my question is creating an Option<Anything> from a special value. It's also not How to transform fields during deserialization using Serde? as that still concerns a specific type.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
njaard
  • 529
  • 4
  • 15
  • It's hard to answer your question because it doesn't include a [MCVE]. We can't tell **exactly** what your input looks like, or even if it's JSON or YAML or .... It would make it easier for us to help you if you try to reproduce your error on the [Rust Playground](https://play.rust-lang.org) if possible, otherwise in a brand new Cargo project, then [edit] your question to include the additional info. There are [Rust-specific MCVE tips](//stackoverflow.com/tags/rust/info) you can use to reduce your original code for posting here. Thanks! – Shepmaster May 30 '19 at 23:12

1 Answers1

7

You can write your own deserialization function that handles this case:

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

// custom deserializer function
fn deserialize_maybe_nan<'de, D, T: Deserialize<'de>>(
    deserializer: D,
) -> Result<Option<T>, D::Error>
where
    D: Deserializer<'de>,
{
    // we define a local enum type inside of the function
    // because it is untagged, serde will deserialize as the first variant
    // that it can
    #[derive(Deserialize)]
    #[serde(untagged)]
    enum MaybeNA<U> {
        // if it can be parsed as Option<T>, it will be
        Value(Option<U>),
        // otherwise try parsing as a string
        NAString(String),
    }

    // deserialize into local enum
    let value: MaybeNA<T> = Deserialize::deserialize(deserializer)?;
    match value {
        // if parsed as T or None, return that
        MaybeNA::Value(value) => Ok(value),

        // otherwise, if value is string an "n/a", return None
        // (and fail if it is any other string)
        MaybeNA::NAString(string) => {
            if string == "n/a" {
                Ok(None)
            } else {
                Err(serde::de::Error::custom("Unexpected string"))
            }
        }
    }
}

Then you can mark your fields with #[serde(default, deserialize_with = "deserialize_maybe_nan")] to use this function instead of the default function:

#[derive(Deserialize)]
struct Data {
    #[serde(default, deserialize_with = "deserialize_maybe_nan")]
    field1: Option<f32>,
    #[serde(default, deserialize_with = "deserialize_maybe_nan")]
    field2: Option<u64>,
    // more ...
}

Working playground example

More information in the documentation:

Ondrej Slinták
  • 31,386
  • 20
  • 94
  • 126
Frxstrem
  • 38,761
  • 9
  • 79
  • 119
  • This not only answers my question, but shows how to make a generalized wrapper over serde's existing deserializers. – njaard May 31 '19 at 03:30
  • @njaard it's exactly what https://stackoverflow.com/questions/46753955/how-to-transform-fields-during-deserialization-using-serde say – Stargateur May 31 '19 at 03:40
  • This is useful but does not work when used for `String` and the value is a valid integer. Any idea how to proceed here? – Manuel May 30 '23 at 15:35
  • @Manuel What do you mean? When the integer is encoded as a string? – Frxstrem May 30 '23 at 17:12