在反序列化时是否有更简单的方法转换类型?

13
使用serde_json,我有一些包含String的JSON对象需要转换为浮点数。我已经发现了一个自定义反序列化器的解决方案,但它似乎是一种hack方法。这里是下面代码的工作示例。
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

use serde_json::Error;
use serde::de::{Deserialize, DeserializeOwned, Deserializer};

#[derive(Serialize, Deserialize)]
struct Example {
    #[serde(deserialize_with = "coercible")]
    first: f64,
    second: f64,
}

fn coercible<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
    T: DeserializeOwned,
    D: Deserializer<'de>,
{
    use serde::de::Error;
    let j = String::deserialize(deserializer)?;
    serde_json::from_str(&j).map_err(Error::custom)
}

fn typed_example() -> Result<(), Error> {
    let data = r#"["3.141",1.618]"#;
    let e: Example = serde_json::from_str(data)?;
    println!("{} {}", e.first * 2.0, e.second * 2.0);
    Ok(())
}

fn main() {
    typed_example().unwrap();
}

上述代码编译并运行的结果如预期,输出两个浮点数。
我正在尝试学习反序列化解决方案,但我想知道我是否朝着正确的方向前进,或者是否有更好的方法来实现这一点。

我尝试理解serde已经一年了... - Stargateur
我不确定这个问题是否适合在SO上提问。你已经有了一个可行的解决方案,而且你正在寻求开放式建议。我认为这可能会吸引那些SO试图避免的回答(如果有的话)。这可能更适合CodeReview - Simon Whitehead
如果你只是想要代码审查,那么最好将其发送到Code Review。你的方法似乎可行,因此我们无法回答任何具体问题。 - E net4
谢谢您的建议。我对代码的理解正在提高,因此我可能能够将问题重新表述为更直接可回答的形式。 - user1992266
2个回答

11

使用可强制转换是一种巧合。有了它,输入的"3.141"被剥离了其"",所以我让serde_json::from_str(&j)接受3.141作为输入值,并适当地返回float类型。这种意外的解决方案在输入JSON包含意外值时容易出错和令人困惑。

我阅读了Serde文档(非常好的学习练习),并找到了将字符串转换为f64的适当方法,以在JSON反序列化时进行转换(此处提供一个可以工作的playground):

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

use std::fmt;
use serde_json::Error;
use serde::de::{self, Deserializer, Unexpected, Visitor};

#[derive(Serialize, Deserialize)]
struct Example {
    #[serde(deserialize_with = "string_as_f64")]
    first: f64,
    second: f64,
}

fn string_as_f64<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
    D: Deserializer<'de>,
{
    deserializer.deserialize_f64(F64Visitor)
}

struct F64Visitor;
impl<'de> Visitor<'de> for F64Visitor {
    type Value = f64;
    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a string representation of a f64")
    }
    fn visit_str<E>(self, value: &str) -> Result<f64, E>
    where
        E: de::Error,
    {
        value.parse::<f64>().map_err(|_err| {
            E::invalid_value(Unexpected::Str(value), &"a string representation of a f64")
        })
    }
}

fn typed_example() -> Result<(), Error> {
    let data = r#"["3.141",1.618]"#;
    let e: Example = serde_json::from_str(data)?;
    println!("{} {}", e.first * 2.0, e.second * 2.0);
    Ok(())
}

fn main() {
    typed_example().unwrap();
}

向 Serde 开发人员致敬,因为尽管我觉得 Serde 文档 完全晦涩难懂,但实际上它证明非常有帮助和易于理解。我只是从头开始慢慢阅读。


2
你的游乐场链接无法使用:线程 'main' 在 '/checkout/src/libcore/result.rs:916:5' 处恐慌:在 'Err' 值上调用了 Result::unwrap():ErrorImpl { code: Message("invalid type: string "3.141", expected a string representation of a f64"), line: 1, column: 8 } 注意:运行时加上 RUST_BACKTRACE=1 可以获取回溯信息。 - senden9
2
代码中有一个小错误:string_as_f64 实际上应该调用 deserializer.deserialize_str(F64Visitor),而不是 deserializer.deserialize_f64(F64Visitor) - Tomasz Lewowski
Rust自2017年以来发生了很大的变化,现在有更加简洁的方法来完成这个任务,幸运的是! - Cornelius Roemer

3

轻量级的 serde-this-or-that crate 使其成为一行代码:

use serde_this_or_that::as_f64;

#[derive(Serialize, Deserialize)]
struct Example {
    #[serde(deserialize_with = "as_f64")]
    first: f64,
    second: f64,
}

请注意,serde-this-or-that 以潜在令人惊讶的方式解决边缘情况。例如,当反序列化 as_f64 时,空字符串会被强制转换为 0.0

一个更简洁的自定义(因此可定制)版本(改编自 Reddit 评论)如下:

struct Example {
    #[serde(deserialize_with = "de_f64_or_string_as_f64")]
    first: f64,
    second: f64,
}

fn de_f64_or_string_as_f64<'de, D: Deserializer<'de>>(deserializer: D) -> Result<f64, D::Error> {
  Ok(match Value::deserialize(deserializer)? {
    Value::String(s) => s.parse().map_err(de::Error::custom)?,
    Value::Number(num) => num.as_f64().ok_or_else(|| de::Error::custom("Invalid number"))?,
    _ => return Err(de::Error::custom("wrong type")),
  })
}

当字符串解析失败时,在 Option<f64> 中返回 None 可能更安全。上面的代码会出现错误,例如 "first": "unparseable"

下面的代码将返回 None

struct Example {
    #[serde(default, deserialize_with = "de_f64_or_string_as_f64")]
    first: Option<f64>,
    second: Option<f64>,
}

fn de_f64_or_string_as_f64<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<f64>, D::Error> {
  Ok(match Value::deserialize(deserializer)? {
    Value::String(s) => s.parse().ok(),
    Value::Number(num) => num.as_f64(),
    _ => None,
  })
}

请注意,我派生了 default,以便解析错误会导致 None

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