ser/de fields with three-state

Rust

背景

使用 serde_json 反序列化数据时,字段定义为 Option<T> 时,“null值” 和 “字段缺失” 都会被映射到 Option::<T>::None。 但在某些场景下,“null值” 和 “字段缺失”需要被识别并进行不同的处理。 (反向:序列化数据时,可以使用 #[serde(skip_serializing_if = "Option::is_none")] 跳过该字段的处理)

// 反序列化问题示例
fn main() {
    #[derive(Deserialize, Debug)]
    struct User {
        name: Option<String>,
    }
 
    let user1 = serde_json::from_str::<User>(r#"{"name": "lopo"}"#).unwrap();
    let user2 = serde_json::from_str::<User>(r#"{"name": null}"#).unwrap();
    let user3 = serde_json::from_str::<User>(r#"{}"#).unwrap();
 
    println!("{:?}", user1); // User { name: Some("lopo") }
    println!("{:?}", user2); // User { name: None }
    println!("{:?}", user3); // User { name: None }
}

实现

本文中通过定义三态枚举 Tres 并为其实现 Serialize/Deserialize 来实现功能。思路如下:

具体实现如下:

mod tres {
    use serde::de::{self, Visitor};
    use serde::ser::Serialize;
    use serde::{Deserialize, Deserializer, Serializer};
    use std::fmt;
    use std::marker::PhantomData;
 
    #[derive(Debug, Default, PartialEq, Eq)]
    pub enum Tres<T> {
        #[default]
        Absent,
        Null,
        Value(T),
    }
 
    // region deserialize 处理
    impl<'de, T> Deserialize<'de> for Tres<T>
    where
        T: Deserialize<'de>,
    {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: Deserializer<'de>,
        {
            struct TresVisitor<T>(PhantomData<T>);
 
            impl<'de, T> Visitor<'de> for TresVisitor<T>
            where
                T: Deserialize<'de>,
            {
                type Value = Tres<T>;
 
                fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                    formatter.write_str("null or a value")
                }
 
                fn visit_none<E>(self) -> Result<Self::Value, E>
                where
                    E: de::Error,
                {
                    Ok(Tres::Null)
                }
 
                fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
                where
                    D: Deserializer<'de>,
                {
                    T::deserialize(deserializer).map(Tres::Value)
                }
            }
 
            deserializer.deserialize_option(TresVisitor(PhantomData))
        }
    }
    // endregion
 
    // region serialize 处理
    impl<T> Tres<T> {
        pub fn is_absent(tres: &Self) -> bool {
            matches!(tres, Tres::Absent)
        }
    }
 
    impl<T> Serialize for Tres<T>
    where
        T: Serialize,
    {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            match self {
                Tres::Absent => Err(serde::ser::Error::custom(
                    "cannot serialize Tres::Absent; use #[serde(skip_serializing_if = \"Tres::is_absent\")] on struct fields",
                )),
                Tres::Null => serializer.serialize_none(),
                Tres::Value(value) => value.serialize(serializer),
            }
        }
    }
    //endregion
}

测试

测试主要包含五个场景:

反序列化

#[cfg(test)]
mod de_tests {
    use super::tres::*;
    use serde::Deserialize;
 
    #[derive(Debug, Deserialize, PartialEq, Eq)]
    struct Example {
        #[serde(default)]
        field: Tres<String>,
    }
 
    #[test]
    fn absent_when_field_missing() {
        let value: Example = serde_json::from_str(r#"{"other": 1}"#).unwrap();
        assert_eq!(value.field, Tres::Absent);
    }
 
    #[test]
    fn null_when_field_is_null() {
        let value: Example = serde_json::from_str(r#"{"field": null}"#).unwrap();
        assert_eq!(value.field, Tres::Null);
    }
 
    #[test]
    fn value_when_field_is_present() {
        let value: Example = serde_json::from_str(r#"{"field": "hello"}"#).unwrap();
        assert_eq!(value.field, Tres::Value("hello".into()));
    }
 
    #[test]
    fn standalone_null() {
        let value: Tres<i32> = serde_json::from_str("null").unwrap();
        assert_eq!(value, Tres::Null);
    }
 
    #[test]
    fn standalone_value() {
        let value: Tres<i32> = serde_json::from_str("42").unwrap();
        assert_eq!(value, Tres::Value(42));
    }
}

序列化

#[cfg(test)]
mod ser_tests {
    use super::tres::*;
    use serde::Serialize;
 
    #[derive(Debug, Serialize, PartialEq, Eq)]
    struct Example {
        #[serde(skip_serializing_if = "Tres::is_absent")]
        field: Tres<String>,
    }
 
    #[test]
    fn absent_serializes_as_missing_field() {
        let value = Example {
            field: Tres::Absent,
        };
        let json = serde_json::to_string(&value).unwrap();
        assert_eq!(json, "{}");
    }
 
    #[test]
    fn null_serializes_as_null() {
        let value = Example { field: Tres::Null };
        let json = serde_json::to_string(&value).unwrap();
        assert_eq!(json, r#"{"field":null}"#);
    }
 
    #[test]
    fn value_serializes_normally() {
        let value = Example {
            field: Tres::Value("hello".into()),
        };
        let json = serde_json::to_string(&value).unwrap();
        assert_eq!(json, r#"{"field":"hello"}"#);
    }
 
    #[test]
    fn standalone_null_serializes() {
        let json = serde_json::to_string(&Tres::<i32>::Null).unwrap();
        assert_eq!(json, "null");
    }
 
    #[test]
    fn standalone_value_serializes() {
        let json = serde_json::to_string(&Tres::Value(42)).unwrap();
        assert_eq!(json, "42");
    }
}

使用

需要结合自行添加的 serde 过程宏元数据注解来实现完整功能:

// 使用 Tres 区分 Null 和 Absent
 
fn main() {
    #[derive(Deserialize, Debug)]
    struct User {
        #[serde(default)]
        name: Tres<String>,
    }
 
    let user1 = serde_json::from_str::<User>(r#"{"name": "lopo"}"#).unwrap();
    let user2 = serde_json::from_str::<User>(r#"{"name": null}"#).unwrap();
    let user3 = serde_json::from_str::<User>(r#"{}"#).unwrap();
 
    println!("{:?}", user1); // User { name: Value("lopo") }
    println!("{:?}", user2); // User { name: Null }
    println!("{:?}", user3); // User { name: Absent }
}

局限

impl<T> Into<Option<T>> for Tres<T> {
    fn into(self) -> Option<T> {
        match self {
            Tres::Absent | Tres::Null => None,
            Tres::Value(v) => Some(v),
        }
    }
}
impl<T> Tres<T> {
    pub fn is_null(&self) -> bool {
        matches!(self, Tres::Null)
    }
 
    pub fn unwrap(self) -> T {
        match self {
            Tres::Absent => panic!("called `Tres::unwrap()` on a `Absent` value"),
            Tres::Null => panic!("called `Tres::unwrap()` on a `Null` value"),
            Tres::Value(v) => v,
        }
    }
}

References