mai*_*n-- 7 json rust serde serde-json
我必须反序列化JSON blob,在某些地方,缺少整个对象被编码为具有相同结构的对象,但其所有字段都设置为默认值(空字符串和零).
extern crate serde_json; // 1.0.27
#[macro_use] extern crate serde_derive; // 1.0.78
extern crate serde; // 1.0.78
#[derive(Debug, Deserialize)]
struct Test<T> {
text: T,
number: i32,
}
#[derive(Debug, Deserialize)]
struct Outer {
test: Option<Test<String>>,
}
#[derive(Debug, Deserialize)]
enum Foo { Bar, Baz }
#[derive(Debug, Deserialize)]
struct Outer2 {
test: Option<Test<Foo>>,
}
fn main() {
println!("{:?}", serde_json::from_str::<Outer>(r#"{ "test": { "text": "abc", "number": 42 } }"#).unwrap());
// good: Outer { test: Some(Test { text: "abc", number: 42 }) }
println!("{:?}", serde_json::from_str::<Outer>(r#"{ "test": null }"#).unwrap());
// good: Outer { test: None }
println!("{:?}", serde_json::from_str::<Outer>(r#"{ "test": { "text": "", "number": 0 } }"#).unwrap());
// bad: Outer { test: Some(Test { text: "", number: 0 }) }
// should be: Outer { test: None }
println!("{:?}", serde_json::from_str::<Outer2>(r#"{ "test": { "text": "Bar", "number": 42 } }"#).unwrap());
// good: Outer2 { test: Some(Test { text: Bar, number: 42 }) }
println!("{:?}", serde_json::from_str::<Outer2>(r#"{ "test": { "text": "", "number": 0 } }"#).unwrap());
// bad: error
// should be: Outer { test: None }
}
Run Code Online (Sandbox Code Playgroud)
我会在反序列化后处理这个问题但是你可以看到这种方法对于枚举值是不可能的:没有变量匹配空字符串,因此反序列化完全失败.
我怎么能教这个给serde?
这里需要解决两件事:用Some(value)ifNone替换value都是默认值,以及处理 的空字符串情况Foo。
第一件事很容易。Deserialize无条件反序列化Option它,就好像Some输入字段不是一样None,因此您需要创建一个自定义Deserialize实现,如果 the等于某个哨兵,则替换Some(value)为,如默认值(这是 Issac 提出的答案,但在这里正确实现):Nonevalue
fn none_if_all_default<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
T: Deserialize<'de> + Default + Eq,
D: Deserializer<'de>,
{
Option::deserialize(deserializer).map(|opt| match opt {
Some(value) if value == T::default() => None,
opt => opt,
})
}
#[derive(Deserialize)]
struct Outer<T: Eq + Default> {
#[serde(deserialize_with = "none_if_all_default")]
#[serde(bound(deserialize = "T: Deserialize<'de>"))]
test: Option<Test<T>>,
}
Run Code Online (Sandbox Code Playgroud)
这解决了你的问题的前半部分,与Option<Test<String>>. 这适用于任何可反序列化类型Eq + Default。
这个enum案子要棘手得多。您面临的问题是,根本不会从orFoo以外的字符串进行反序列化。除了向枚举添加第三个“死”变体之外,我真的没有看到一个好的解决方案:"Bar""Baz"
#[derive(PartialEq, Eq, Deserialize)]
enum Foo {
Bar,
Baz,
#[serde(rename = "")]
Absent,
}
impl Default for Foo { fn default() -> Self { Self::Absent } }
Run Code Online (Sandbox Code Playgroud)
从数据建模的角度来看,这个问题存在的原因是它必须考虑到您将得到这样的 json 的可能性:
{ "test": { "text": "", "number": 42 } }
Run Code Online (Sandbox Code Playgroud)
在这种情况下,显然Outer { test: None }不是正确的结果,但它仍然需要一个值来存储Foo,否则返回反序列化错误。
如果您希望只有在is时""才是有效文本,那么与仅使用 相比,您可以做一些更加复杂且可能超出您需求的事情。您需要使用未标记的枚举,它可以存储“valid”或“allempty” ,然后创建一个仅反序列化默认值的结构版本:number0AbsentTestTest
struct MustBeDefault<T> {
marker: PhantomData<T>
}
impl<'de, T> Deserialize<'de> for MustBeDefault<T>
where
T: Deserialize<'de> + Eq + Default
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>
{
match T::deserialize(deserializer)? == T::default() {
true => Ok(MustBeDefault { marker: PhantomData }),
false => Err(D::Error::custom("value must be default"))
}
}
}
// All fields need to be generic in order to use this solution.
// Like I said, this is radically overkill.
#[derive(Deserialize)]
struct Test<T, U> {
text: T,
number: U,
}
#[derive(Deserialize)]
#[serde(untagged)]
enum MaybeDefaultedTest<T> {
AllDefault(Test<EmptyString, MustBeDefault<i32>>),
Normal(Test<Foo, i32>),
}
// `EmptyString` is a type that only deserializes from empty strings;
// its implementation is left as an exercise to the reader.
// You'll also need to convert from MaybeDefaultedTest<T> to Option<T>;
// this is also left as an exercise to the reader.
Run Code Online (Sandbox Code Playgroud)
现在可以编写MaybeDefaulted<Foo>, 它将从{"text": "", "number": 0}or 或{"text": "Baz", "number": 10}之类的东西反序列化{"text": "Baz", "number": 0},但无法从 反序列化{"text": "", "number": 10}。
同样,第三次,这个解决方案可能完全是多余的(特别是如果您的实际用例涉及结构中的两个以上字段Test),因此除非您有非常强烈的数据建模要求,否则您应该添加一个Absent变体到Foo。