使用Serde将两种类型转换为单一类型

时间:2016-06-16 22:31:22

标签: json rust deserialization serde

我正在编写一个挂钩到发送回JSON的Web服务的程序。

当某个属性不存在时,它会提供一个空对象,其所有字段都为空字符串,而不是排除该值。当属性存在时,某些属性为u64。我怎么能这样,Serde处理这个案子?

Rust Structs

#[derive(Clone, Debug, Deserialize)]
struct WebResponse {
    foo: Vec<Foo>,
}

#[derive(Clone, Debug, Deserialize)]
struct Foo {
    points: Points,
}

#[derive(Clone, Debug, Deserialize)]
struct Points {
    x: u64,
    y: u64,
    name: String,
}

示例JSON

{
    "foo":[
        {
            "points":{
                "x":"",
                "y":"",
                "name":""
            }
        },
        {
            "points":{
                "x":78,
                "y":92,
                "name":"bar"
            }
        }
    ]
}

2 个答案:

答案 0 :(得分:9)

Serde支持一个有趣的attributes选择,可用于自定义类型的序列化或反序列化,同时仍然大部分使用派生实现。

在您的情况下,您需要能够解码可以指定为多种类型之一的字段,并且您不需要来自其他字段的信息来决定如何解码有问题的字段。 u64注释非常适合解决您的问题。

我们需要定义一个函数,将空字符串或整数值解码为extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_json; use serde::Deserializer; use serde::de::{self, Unexpected}; use std::fmt; #[derive(Clone, Debug, Deserialize)] struct WebResponse { foo: Vec<Foo>, } #[derive(Clone, Debug, Deserialize)] struct Foo { points: Points, } #[derive(Clone, Debug, Deserialize)] struct Points { #[serde(deserialize_with = "deserialize_u64_or_empty_string")] x: u64, #[serde(deserialize_with = "deserialize_u64_or_empty_string")] y: u64, name: String, } struct DeserializeU64OrEmptyStringVisitor; impl<'de> de::Visitor<'de> for DeserializeU64OrEmptyStringVisitor { type Value = u64; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("an integer or a string") } fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> where E: de::Error, { Ok(v) } fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where E: de::Error, { if v == "" { Ok(0) } else { Err(E::invalid_value(Unexpected::Str(v), &self)) } } } fn deserialize_u64_or_empty_string<'de, D>(deserializer: D) -> Result<u64, D::Error> where D: Deserializer<'de>, { deserializer.deserialize_any(DeserializeU64OrEmptyStringVisitor) } fn main() { let value = serde_json::from_str::<WebResponse>( r#"{ "foo": [ { "points": { "x": "", "y": "", "name": "" } }, { "points": { "x": 78, "y": 92, "name": "bar" } } ] }"#, ); println!("{:?}", value); } 。我们可以对两个字段使用相同的函数,因为我们需要相同的行为。此函数将使用自定义Visitor来处理字符串和整数。它有点长,但它让你欣赏Serde为你做的所有工作!

Cargo.toml

[dependencies] serde = "1.0.15" serde_json = "1.0.4" serde_derive = "1.0.15"

{{1}}

答案 1 :(得分:2)

str_or_u64 中,我们使用 untagged enum 来表示字符串或数字。然后我们可以将该字段反序列化为该枚举并将其转换为数字。

我们使用 deserialize_with 注释 Points 中的两个字段以告诉它使用我们的特殊转换:

use serde::{Deserialize, Deserializer}; // 1.0.124
use serde_json; // 1.0.64

#[derive(Debug, Deserialize)]
struct WebResponse {
    foo: Vec<Foo>,
}

#[derive(Debug, Deserialize)]
struct Foo {
    points: Points,
}

#[derive(Debug, Deserialize)]
struct Points {
    #[serde(deserialize_with = "str_or_u64")]
    x: u64,
    #[serde(deserialize_with = "str_or_u64")]
    y: u64,
    name: String,
}

fn str_or_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
    D: Deserializer<'de>,
{
    #[derive(Deserialize)]
    #[serde(untagged)]
    enum StrOrU64<'a> {
        Str(&'a str),
        U64(u64),
    }

    Ok(match StrOrU64::deserialize(deserializer)? {
        StrOrU64::Str(v) => v.parse().unwrap_or(0), // Ignoring parsing errors
        StrOrU64::U64(v) => v,
    })
}

fn main() {
    let input = r#"{
        "foo":[
            {
                "points":{
                    "x":"",
                    "y":"",
                    "name":""
                }
            },
            {
                "points":{
                    "x":78,
                    "y":92,
                    "name":"bar"
                }
            }
        ]
    }"#;

    dbg!(serde_json::from_str::<WebResponse>(input));
}

另见: