I try to handle some existing json where a specific field is sometimes given as a number { "foo": 17 } and sometimes as a string containing the number { "foo": "17" }. So what I want is that if the value is an integer, just use that and if the value is a string, try to parse it.
The following works for the quoted-string case:
/// The type I'm working with is a newtype wrapper around NonZeroU32.
#[derive(Clone, Copy, Debug, Serialize)]
#[serde(transparent)]
struct MyType(NonZeroU32);
impl<'de> Deserialize<'de> for MyNumeric {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
D::Error: serde::de::Error,
{
use serde::de::Error;
let s = <&str>::deserialize(deserializer)?;
let n = s.parse().map_err(|_| D::Error::custom("non-integer"))?;
Ok(MyNumeric(NonZeroU32::new(n).ok_or_else(|| D::Error::custom("unexpected zero"))?))
}
}
My problem is that <&str>::deserialize(deserializer) consumes the deserializer, so I can't attempt u32::deserialize(deserializer) if the string deserialize fails. I guess I could deserialize to a serde_json::Value and inspect that, but is there a better way?
Yes, going over a serde_json::Value works. I would prefer to be format-agnostic, but on second thought, that may not even be possible? In some binary formats it may be impossible to distinguish between e.g. the string "16" and the number 0x22313722, which would both be legal but very different?
My current Deserialize implementation looks like this and seems to work:
impl<'de> Deserialize<'de> for MyNumeric {
/// A custom deserializer, since the value sometimes appear as a quoted string
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
D::Error: serde::de::Error,
{
use serde::de::Error;
use serde_json::Value;
use std::convert::TryInto;
let v = Value::deserialize(deserializer)?;
let n = v
.as_u64()
.or_else(|| v.as_str().and_then(|s| s.parse().ok()))
.ok_or_else(|| D::Error::custom("non-integer"))?
.try_into()
.map_err(|_| D::Error::custom("overflow"))?;
Ok(MyNumeric(
NonZeroU32::new(n).ok_or_else(|| D::Error::custom("unexpected zero"))?,
))
}
}