如何编写一个 Serde 访问器将字符串数组的数组转换为 Vec<Vec<f64>>?

8
我需要将一个JSON反序列化成一个结构体,其中包含一个Vec<Vec<f64>>字段。JSON中的数字是字符串形式的,因此我需要一个自定义反序列化程序,在反序列化期间将字符串转换为f64
以下是要反序列化的示例JSON:
{
  "values": [["2", "1.4"], ["8.32", "1.5"]]
}

我的结构体如下:

#[derive(Deserialize)]
struct Payload {
    #[serde(default, deserialize_with = "from_array_of_arrays_of_strs")]
    values: Vec<Vec<f64>>,
}

我看到Serde的示例中可能可以使用访问者来做这件事情,因此我实现了这个访问者:

fn from_array_of_arrays_of_strs<'de, T, D>(deserializer: D) -> Result<Vec<Vec<f64>>, D::Error>
where
    T: Deserialize<'de>,
    D: Deserializer<'de>,
{
    struct F64Visitor(PhantomData<fn() -> Vec<Vec<f64>>>);

    impl<'de> Visitor<'de> for F64Visitor {
        type Value = Vec<Vec<f64>>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("a nonempty sequence of numbers")
        }

        #[inline]
        fn visit_str<E>(self, value: &str) -> Result<f64, E>
        where
            E: serde::de::Error,
        {
            self.visit_string(String::from(value))
        }

        #[inline]
        fn visit_string<E>(self, value: String) -> Result<f64, E> {
            Ok(value.parse::<f64>().unwrap())
        }

        #[inline]
        fn visit_seq<V, T>(self, mut visitor: V) -> Result<Vec<T>, V::Error>
        where
            V: SeqAccess<'de>,
        {
            let mut vec = Vec::new();

            while let Some(elem) = try!(visitor.next_element()) {
                vec.push(elem);
            }

            Ok(vec)
        }
    }

    let visitor = F64Visitor(PhantomData);
    deserializer.deserialize_seq(visitor)
}

playground

编译器抱怨 visit_strvisit_string 的类型与 trait 不兼容:

error[E0053]: method `visit_str` has an incompatible type for trait
  --> src/main.rs:32:9
   |
32 | /         fn visit_str<E>(self, value: &str) -> Result<f64, E>
33 | |             where
34 | |             E: serde::de::Error,
35 | |         {
36 | |             self.visit_string(String::from(value))
37 | |         }
   | |_________^ expected struct `std::vec::Vec`, found f64
   |
   = note: expected type `fn(from_array_of_arrays_of_strs::F64Visitor, &str) -> std::result::Result<std::vec::Vec<std::vec::Vec<f64>>, E>`
              found type `fn(from_array_of_arrays_of_strs::F64Visitor, &str) -> std::result::Result<f64, E>`

error[E0053]: method `visit_string` has an incompatible type for trait
  --> src/main.rs:40:9
   |
40 | /         fn visit_string<E>(self, value: String) -> Result<f64, E> {
41 | |             Ok(value.parse::<f64>().unwrap())
42 | |         }
   | |_________^ expected struct `std::vec::Vec`, found f64
   |
   = note: expected type `fn(from_array_of_arrays_of_strs::F64Visitor, std::string::String) -> std::result::Result<std::vec::Vec<std::vec::Vec<f64>>, E>`
              found type `fn(from_array_of_arrays_of_strs::F64Visitor, std::string::String) -> std::result::Result<f64, E>`

error[E0049]: method `visit_seq` has 2 type parameters but its trait declaration has 1 type parameter
  --> src/main.rs:45:21
   |
45 |         fn visit_seq<V, T>(self, mut visitor: V) -> Result<Vec<T>, V::Error>
   |                     ^^^^^^ found 2 type parameters, expected 1

我认为我对访问者模式的工作原理没有正确的理解。我是否只需要一个访问者来反序列化字符串数组的数组,还是我需要一个用于反序列化数组,另一个用于反序列化字符串到f64
我已阅读:
2个回答

9

如何在使用serde进行反序列化之前转换字段 中所述,最简单的解决方案是为字符串作为浮点数值引入一个新类型。然后您可以为该类型实现Deserialize, 利用现有的Deserialize实现和字符串解析:

extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;

use serde::de::{Deserialize, Deserializer, Error, Unexpected};

#[derive(Debug, Deserialize)]
struct Payload {
    #[serde(default)]
    values: Vec<Vec<Value>>,
}

#[derive(Debug)]
struct Value(f64);

impl<'de> Deserialize<'de> for Value {
    fn deserialize<D>(deserializer: D) -> Result<Value, D::Error>
        where D: Deserializer<'de>
    {
        let s: &str = Deserialize::deserialize(deserializer)?;
        s.parse()
            .map(Value)
            .map_err(|_| D::Error::invalid_value(Unexpected::Str(s), &"a floating point number as a string"))
    }
}

fn main() {
    let input = r#"
{
  "values": [["2", "1.4"], ["8.32", "1.5"]]
}
"#;

    let out: Payload = serde_json::from_str(input).unwrap();

    println!("{:?}", out);
}

我更喜欢这个解决方案,因为在很多情况下,我希望新的类型在我的系统中起到作用。


如果你确实需要对 Vec<Vec<f64>> 进行一次精确的反序列化,你需要实现两个访问者。一个将反序列化外部的 Vec,另一个将反序列化内部的 Vec。我们将重复使用之前的 Value newtype,但内部访问者将去除它。外部访问者将对内部访问者周围的新类型执行相同的操作:

extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;

use serde::de::{Deserialize, Deserializer, Error, SeqAccess, Unexpected, Visitor};
use std::fmt;

#[derive(Debug, Deserialize)]
struct Payload {
    #[serde(default, deserialize_with = "from_array_of_arrays_of_strs")]
    values: Vec<Vec<f64>>,
}

fn from_array_of_arrays_of_strs<'de, D>(deserializer: D) -> Result<Vec<Vec<f64>>, D::Error>
where
    D: Deserializer<'de>,
{
    struct OuterVisitor;

    impl<'de> Visitor<'de> for OuterVisitor {
        type Value = Vec<Vec<f64>>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("a nonempty sequence of a sequence of numbers")
        }

        #[inline]
        fn visit_seq<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
        where
            V: SeqAccess<'de>,
        {
            let mut vec = Vec::new();

            while let Some(Inner(elem)) = try!(visitor.next_element()) {
                vec.push(elem);
            }

            Ok(vec)
        }
    }

    deserializer.deserialize_seq(OuterVisitor)
}

struct Inner(Vec<f64>);

impl<'de> Deserialize<'de> for Inner {
    fn deserialize<D>(deserializer: D) -> Result<Inner, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct InnerVisitor;

        impl<'de> Visitor<'de> for InnerVisitor {
            type Value = Inner;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a nonempty sequence of numbers")
            }

            #[inline]
            fn visit_seq<V>(self, mut visitor: V) -> Result<Inner, V::Error>
            where
                V: SeqAccess<'de>,
            {
                let mut vec = Vec::new();

                while let Some(Value(elem)) = try!(visitor.next_element()) {
                    vec.push(elem);
                }

                Ok(Inner(vec))
            }
        }

        deserializer.deserialize_seq(InnerVisitor)
    }
}

struct Value(f64);

impl<'de> Deserialize<'de> for Value {
    fn deserialize<D>(deserializer: D) -> Result<Value, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s: &str = Deserialize::deserialize(deserializer)?;
        s.parse().map(Value).map_err(|_| {
            D::Error::invalid_value(Unexpected::Str(s), &"a floating point number as a string")
        })
    }
}

fn main() {
    let input = r#"
{
  "values": [["2", "1.4"], ["8.32", "1.5"]]
}
"#;

    let out: Payload = serde_json::from_str(input).unwrap();

    println!("{:?}", out);
}

3

如果不想自己编写访问者,也可以使用字符串而不是数字解析JSON文件。

use serde_with::{serde_as, DisplayFromStr};

#[serde_as]
#[derive(Debug, serde::Deserialize)]
struct Payload {
    #[serde_as(as = "Vec<Vec<DisplayFromStr>>")]
    #[serde(default)]
    values: Vec<Vec<f64>>,
}

let j = serde_json::json!({
  "values": [["2", "1.4"], ["8.32", "1.5"]]
});

let p: Payload = serde_json::from_value(j)?;
assert_eq!(p.values, vec![vec![2.0, 1.4], vec![8.32, 1.5]]);

注释意味着我们在一个Vec内部有一个Vec,并且最内层的元素应该使用FromStr进行反序列化和使用Display进行序列化。注释支持任何具有DisplayFromStr实现的类型,因此它也可以用于u64Url类型。


这比另一个答案更简洁。这个翻译是否与之前的答案一样高效? - financial_physician
1
性能应该与被接受的答案相当。这种解决方案的一个优点是避免了反序列化 &str,在某些情况下这是有问题的。您还可以免费获得序列化。 - jonasbb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接