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: