use core::ops::Deref;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Field<T> {
#[default]
Absent,
Null,
Value(T),
}
impl<T> Field<T> {
pub const fn is_absent(&self) -> bool {
matches!(self, Self::Absent)
}
pub const fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
pub const fn get(&self) -> Option<&T> {
match self {
Self::Value(v) => Some(v),
Self::Absent | Self::Null => None,
}
}
pub fn into_option(self) -> Option<T> {
match self {
Self::Value(v) => Some(v),
Self::Absent | Self::Null => None,
}
}
pub fn as_deref(&self) -> Option<&T::Target>
where
T: Deref,
{
self.get().map(Deref::deref)
}
}
impl<T> From<T> for Field<T> {
fn from(v: T) -> Self {
Self::Value(v)
}
}
impl<T> From<Option<T>> for Field<T> {
fn from(v: Option<T>) -> Self {
v.map_or(Self::Absent, Self::Value)
}
}
impl<T: Serialize> Serialize for Field<T> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
match self {
Self::Value(v) => s.serialize_some(v),
Self::Absent | Self::Null => s.serialize_none(),
}
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for Field<T> {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
Ok(Option::<T>::deserialize(d)?.map_or(Self::Null, Self::Value))
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::Field;
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
struct S {
#[serde(default, skip_serializing_if = "Field::is_absent")]
a: Field<String>,
}
#[test]
fn absent_null_value_round_trip() {
for v in [json!({}), json!({"a": null}), json!({"a": "x"})] {
let s: S = serde_json::from_value(v.clone()).unwrap();
assert_eq!(serde_json::to_value(&s).unwrap(), v);
}
let s: S = serde_json::from_value(json!({"a": "x"})).unwrap();
assert_eq!(s.a.as_deref(), Some("x"));
assert!(serde_json::from_value::<S>(json!({"a": 1})).is_err());
}
}