When developing services that communicate with JSON or similar flexible data formats, it's a common pattern to communicate with "changesets"; or updates to state with only the changed fields present. To represent this kind of data in Rust one can have a struct with every field wrapped as Option<T>, where the None variant signifies a missing field. For one, Diesel supports this pattern when updating data in the database.
However, when the datatype itself is nullable, the meaning of a "missing field" (=don't update) and "null value" (= update by setting to null) get conflated. Fortunately, there is a pattern to support this: double option: Option<Option<T>>. Here the outer option signifies the existence of the field, and the inner option represents the value, or a null.
Serde doesn't support this pattern by default; the visitor visits only the outer Option, and sets it None in case of the default behaviour. However, I think that it would be valuable to support the double option pattern, where it tries to set the inner Option.
Below is an implementation that I'm using with #[serde(deserialize_with = "double_option")].
In case of Option<Option<T>> at the moment, the inner Option never gets set as None – it's essentially transparent, and thus serves no purpose. So I see the double option pattern as more sensible for this case.
pub fn double_option<'de, T: Deserialize<'de>, D>(de: D) -> Result<Option<Option<T>>, D::Error>
where D: Deserializer<'de>
{
#[derive(Debug)]
struct DoubleOptionVisitor<T> {
_inner: PhantomData<T>,
};
impl<'de, T: Deserialize<'de>> Visitor<'de> for DoubleOptionVisitor<T> {
type Value = Option<Option<T>>;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter,
"Either a missing field, a field with null/None, or a field with value T")
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where E: Error
{
Ok(Some(None))
}
fn visit_some<D>(self, de: D) -> Result<Self::Value, D::Error>
where D: Deserializer<'de>
{
match T::deserialize(de) {
Ok(val) => Ok(Some(Some(val))),
Err(e) => Err(e),
}
}
}
de.deserialize_option(DoubleOptionVisitor::<T> { _inner: PhantomData })
}
When developing services that communicate with JSON or similar flexible data formats, it's a common pattern to communicate with "changesets"; or updates to state with only the changed fields present. To represent this kind of data in Rust one can have a struct with every field wrapped as
Option<T>, where theNonevariant signifies a missing field. For one, Diesel supports this pattern when updating data in the database.However, when the datatype itself is nullable, the meaning of a "missing field" (=don't update) and "null value" (= update by setting to null) get conflated. Fortunately, there is a pattern to support this: double option:
Option<Option<T>>. Here the outer option signifies the existence of the field, and the inner option represents the value, or a null.Serde doesn't support this pattern by default; the visitor visits only the outer
Option, and sets itNonein case of the default behaviour. However, I think that it would be valuable to support the double option pattern, where it tries to set the innerOption.Below is an implementation that I'm using with
#[serde(deserialize_with = "double_option")].In case of
Option<Option<T>>at the moment, the innerOptionnever gets set asNone– it's essentially transparent, and thus serves no purpose. So I see the double option pattern as more sensible for this case.