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 来实现功能。思路如下:
- 反序列化:
- 利用
Deserializer::deserialize_option(serde_core内置实现,见参考链接) 实现Tres::Value和Tres::Null的映射 - 利用
#[serde(default)]实现缺失字段自动填充为Tres::Absent
- 利用
- 序列化:定义
Tres::is_absent以支持skip_serializing_if(同Option::is_none用法)
具体实现如下:
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
}测试
测试主要包含五个场景:
- 字段缺失
- 字段为
null值 - 字段为非
null值 - 直接处理
null - 直接处理非
null值
反序列化
#[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<T>的字段上标注#[serde(skip_serializing_if = "Tres::is_absent")], 若不要求跳过Absent的序列化则可以不添加。(Tres::Absent将序列化为null) - 反序列化:在类型为
Tres<T>的字段上标注#[serde(default)](注意,该注解并不要求T实现Default)
// 使用 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 }
}局限
- 本文主要记录实现
ser/de所需内容,其他trait自行添加(如Into<Option<T>>)
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,
}
}
}- 需要结合
serde attributes实现功能,如有需要可自行封装过程宏