【问题标题】:How do I use Serde to deserialize structs with references from a reader?如何使用 Serde 反序列化带有来自读者的引用的结构?
【发布时间】:2023-03-09 15:53:01
【问题描述】:

我有这些结构:

#[derive(Debug, Serialize, Deserialize)]
pub struct GGConf<'a> {
    #[serde(alias = "ssh")]
    #[serde(rename = "ssh")]
    #[serde(default)]
    #[serde(borrow)]
    pub ssh_config: Option<SSHConfig<'a>>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SSHConfig<'a> {
    #[serde(alias = "privateKey")]
    #[serde(rename = "privateKey")]
    private_key: &'a str,

    username: &'a str,
}

当我从 YAML 文件中读取数据时会发生反序列化:

let mut config: GGConf = serde_yaml::from_reader(file)?;

编译时出现错误:

error: implementation of `conf::_IMPL_DESERIALIZE_FOR_GGConf::_serde::Deserialize` is not general enough
   --> src/conf.rs:50:34
    |
50  |           let mut config: GGConf = serde_yaml::from_reader(file)?;
    |                                    ^^^^^^^^^^^^^^^^^^^^^^^ implementation of `conf::_IMPL_DESERIALIZE_FOR_GGConf::_serde::Deserialize` is not general enough
    |
   ::: /home/ninan/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.98/src/de/mod.rs:524:1
    |
524 | / pub trait Deserialize<'de>: Sized {
525 | |     /// Deserialize this value from the given Serde deserializer.
526 | |     ///
527 | |     /// See the [Implementing `Deserialize`][impl-deserialize] section of the
...   |
562 | |     }
563 | | }
    | |_- trait `conf::_IMPL_DESERIALIZE_FOR_GGConf::_serde::Deserialize` defined here
    |
    = note: `conf::GGConf<'_>` must implement `conf::_IMPL_DESERIALIZE_FOR_GGConf::_serde::Deserialize<'0>`, for any lifetime `'0`...
    = note: ...but `conf::GGConf<'_>` actually implements `conf::_IMPL_DESERIALIZE_FOR_GGConf::_serde::Deserialize<'1>`, for some specific lifetime `'1`

我隐约明白 serde 反序列化也有一个生命周期 'de 并且编译器混淆了我为它指定的生命周期?如果我错了,请纠正我。

我目前如何正确地将 YAML 反序列化为两个结构? 有什么我在这里遗漏或误解的吗?

我查看了How do I resolve "implementation of serde::Deserialize is not general enough" with actix-web's Json type?,但我不能使用拥有的类型。我需要它是一个借来的类型。

我将尝试为此编写一个游乐场示例。

【问题讨论】:

  • 很难回答您的问题,因为它不包含minimal reproducible example。我们无法分辨代码中存在哪些 crate(及其版本)、类型、特征、字段等。如果您尝试在Rust Playground 上重现您的错误,如果可能的话,这将使我们更容易为您提供帮助,否则在全新的 Cargo 项目中,然后在edit 您的问题中包含附加信息。您可以使用Rust-specific MRE tips 来减少您在此处发布的原始代码。谢谢!

标签: rust yaml deserialization lifetime serde


【解决方案1】:

这是不可能的;您必须使用自有数据而不是引用。

这是一个最小的例子:

use serde::Deserialize; // 1.0.104

#[derive(Debug, Deserialize)]
pub struct SshConfig<'a> {
    username: &'a str,
}

fn example(file: impl std::io::Read) {
    serde_yaml::from_reader::<_, SshConfig>(file);
}
error: implementation of `_IMPL_DESERIALIZE_FOR_SshConfig::_serde::Deserialize` is not general enough
   --> src/lib.rs:9:5
    |
9   |       serde_yaml::from_reader::<_, SshConfig>(file);
    |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `_IMPL_DESERIALIZE_FOR_SshConfig::_serde::Deserialize` is not general enough
    | 
   ::: /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.104/src/de/mod.rs:531:1
    |
531 | / pub trait Deserialize<'de>: Sized {
532 | |     /// Deserialize this value from the given Serde deserializer.
533 | |     ///
534 | |     /// See the [Implementing `Deserialize`][impl-deserialize] section of the
...   |
569 | |     }
570 | | }
    | |_- trait `_IMPL_DESERIALIZE_FOR_SshConfig::_serde::Deserialize` defined here
    |
    = note: `SshConfig<'_>` must implement `_IMPL_DESERIALIZE_FOR_SshConfig::_serde::Deserialize<'0>`, for any lifetime `'0`...
    = note: ...but `SshConfig<'_>` actually implements `_IMPL_DESERIALIZE_FOR_SshConfig::_serde::Deserialize<'1>`, for some specific lifetime `'1`

如果您查看serde_yaml::from_reader 的定义,您会发现它仅限于反序列化拥有的数据:

pub fn from_reader<R, T>(rdr: R) -> Result<T>
where
    R: Read,
    T: DeserializeOwned,
//     ^^^^^^^^^^^^^^^^ 

serde_json::from_reader 和任何等效函数也是如此。

只有当有数据要引用时,才能反序列化包含引用的类型。实现 Read 特征的东西只能保证它可以将一些字节复制到用户提供的缓冲区中。由于from_reader 函数不接受该缓冲区作为参数,因此任何缓冲区都将在from_reader 的出口处被销毁,从而使引用无效。

另见:


如果您必须使用引用(在很多情况下并非如此),您需要:

  1. 自己从阅读器中读取到缓冲区中
  2. 使用from_str 而不是from_reader
  3. 保持缓冲区与反序列化数据一样长

【讨论】:

  • 将尝试该建议。我需要 SSHConfig 值作为引用,因为我将用文件中的值覆盖一个静态常量。从而迫使我有参考。
  • @leoOrion 听起来你想要How do I create a global, mutable singleton?。在您的情况下,您将无法直接使用从文件加载的引用,因为它们无论如何都不会是 'static
  • 是的。我有一个全局可变单例,其默认 SSHConfig 值可能会或可能不会被文件覆盖。
  • 我在回答中扩展了一点 - serde_yaml 目前不支持零拷贝反序列化(从 v0.8 开始),因此 from_str 方法仍然需要拥有的数据类型跨度>
【解决方案2】:

from_reader 从某个地方(实现 Read 特征的任何地方)获取数据流 - 它不存储数据,这意味着没有任何人拥有数据,因此您无法在结构中引用该数据。换句话说,from_reader 需要一个瞬态数据流,因此需要一个地方来存储数据。

另一个复杂的问题是serde_yaml(至少对于 0.8.11 版本)不支持零拷贝反序列化:

https://docs.rs/serde_yaml/0.8.11/serde_yaml/fn.from_str.html

pub fn from_str<T>(s: &str) -> Result<T> where
    T: DeserializeOwned,

...

YAML 目前不支持零拷贝反序列化。

将其与 serde_json 进行比较,后者会:

https://docs.rs/serde_json/1.0.50/serde_json/de/fn.from_str.html

pub fn from_str<'a, T>(s: &'a str) -> Result<T> where
    T: Deserialize<'a>,

因此,至少对于 serde_json 之类的东西,您可以从拥有的缓冲区中使用 from_str,这将允许您在结构中使用引用(但目前这不适用于 serde_yaml

// Written with rustc 1.42.0 and
// [dependencies]
// serde = "1.0.105"
// serde_derive = "1.0.105"
// serde_json = "1.0.50"

use std::io::Read;
use serde_derive::Deserialize;

#[derive(Debug, Deserialize)]
pub struct SshConfig<'a> {
    username: &'a str,
}

fn main() {
    // Open file handle
    let mut file = std::fs::File::open("example.json").unwrap();

    // Read the data into a String, which stores (and thus owns) the data
    let mut strbuf = String::new();
    file.read_to_string(&mut strbuf).unwrap();

    // Deserialize into struct, which references
    let result: SshConfig = serde_json::from_str(&strbuf).unwrap();
    println!("{:?}", result.username);

    // Note that `result` is only valid as long as `strbuf` exists.
    // i.e if `strbuf` goes out of scope or is moved to another function, we get an error. For example, the following would cause an error:
    // std::mem::drop(strbuf); // Function which moves strbuf, not a referernce
    // println!("{:?}", result.username); // Error
}

根据您所关心的确切内容,这可能比在结构中存储字符串效率低(例如,如果 example.json 大 1MB,并且您只提取单个字段 - 上面的代码将存储整个 1MB内存中的字符串,只有几个字节的文本可访问)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-30
    • 1970-01-01
    相关资源
    最近更新 更多