使用 serde 通过数值作为类型标识符对 json 进行反序列化

2024-05-01

我对 Rust 很陌生,并且有 OOP 背景。所以,也许我误解了一些 Rust 基础知识。

我想用 serde 解析固定的 json 结构。该结构代表不同的消息类型之一。每条消息都有一个数字type属性来区分它。各个消息类型的确切结构大多不同,但它们也可以相同。

{"type": 1, "sender_id": 4, "name": "sender", ...}
{"type": 2, "sender_id": 5, "measurement": 3.1415, ...}
{"type": 3, "sender_id": 6, "measurement": 13.37, ...}
...

首先,我定义了一个enum还可以区分消息类型struct对于没有存储类型的字段的每种类型的消息。

#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
enum Message {
    T1(Type1),
    T2(Type2),
    T3(Type3),
    // ...
}

#[derive(Debug, Serialize, Deserialize)]
struct Type1 {
    sender_id: u32,
    name: String,
    // ...
}
#[derive(Debug, Serialize, Deserialize)]
struct Type2 {
    sender_id: u32,
    measurement: f64,
    // ...
}
#[derive(Debug, Serialize, Deserialize)]
struct Type3 {
    sender_id: u32,
    measurement: f64,
    // ...
}
// ...

当我尝试将字符串转换为Message对象,我收到错误。

let message = r#"{"type":1,"sender_id":123456789,"name":"sender"}"#;
let message: Message = serde_json::from_str(message)?; // error here
// Error: Custom { kind: InvalidData, error: Error("invalid type: integer `1`, expected variant identifier", line: 1, column: 9) }

因此,据我了解,serde 尝试找出当前消息的类型,但它需要一个字符串 为了那个原因。我也尝试写自己的deserialize()-功能。我试图获取数值 对应的type-key 并希望通过类型值创建特定对象。

我必须如何实施deserialize()提取消息的类型并创建特定的消息对象?是否可以在不写的情况下写这个deserialize()- 每个功能Type1/2/3/... struct?

impl<'de> Deserialize<'de> for Message {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
                  where D: Deserializer<'de>,
{
    // which functions I have to call?
}

或者有更好的解决方案来实现我的反序列化吗?

我为这个问题准备了一个游乐场:


Serde 尚不支持整数标签(请参阅问题 #745 https://github.com/serde-rs/serde/issues/745).


如果您能够更改生成数据的内容,那么如果您能够更改type成一个字符串,即"1"代替1。然后你只需使用它就可以让它工作#[serde(rename)] https://serde.rs/field-attrs.html#rename.

#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
enum Message {
    #[serde(rename = "1")]
    T1(Type1),
    #[serde(rename = "2")]
    T2(Type2),
    #[serde(rename = "3")]
    T3(Type3),
    // ...
}

如果这不是一个选项,那么您确实需要创建一个自定义解串器。就代码而言最短,很可能反序列化为serde_json::Value https://docs.serde.rs/serde_json/enum.Value.html,然后匹配type, and 反序列化 the serde_json::Value进入正确的Type{1,2,3}.

use serde_json::Value;

impl<'de> serde::Deserialize<'de> for Message {
    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
        let value = Value::deserialize(d)?;

        Ok(match value.get("type").and_then(Value::as_u64).unwrap() {
            1 => Message::T1(Type1::deserialize(value).unwrap()),
            2 => Message::T2(Type2::deserialize(value).unwrap()),
            3 => Message::T3(Type3::deserialize(value).unwrap()),
            type_ => panic!("unsupported type {:?}", type_),
        })
    }
}

您可能想要执行一些适当的操作错误处理 https://serde.rs/error-handling.html,而不是打开包裹并惊慌失措。


如果您还需要序列化,那么您同样需要一个自定义序列化器。为此,您可以创建一个新类型来序列化,因为您不能使用Message.

use serde::Serializer;

impl Serialize for Message {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        #[derive(Serialize)]
        #[serde(untagged)]
        enum Message_<'a> {
            T1(&'a Type1),
            T2(&'a Type2),
            T3(&'a Type3),
        }

        #[derive(Serialize)]
        struct TypedMessage<'a> {
            #[serde(rename = "type")]
            t: u64,
            #[serde(flatten)]
            msg: Message_<'a>,
        }

        let msg = match self {
            Message::T1(t) => TypedMessage { t: 1, msg: Message_::T1(t) },
            Message::T2(t) => TypedMessage { t: 2, msg: Message_::T2(t) },
            Message::T3(t) => TypedMessage { t: 3, msg: Message_::T3(t) },
        };
        msg.serialize(serializer)
    }
}

使用时#[serde(flatten)] https://serde.rs/field-attrs.html#flatten,然后它使用serde::private::ser::FlatMapSerializer https://github.com/serde-rs/serde/blob/8847800ce2cf404ae5f02485f030e380b246421e/serde/src/private/ser.rs#L1028,它在文档中是隐藏的。您可以使用以下方法来代替创建新类型SerializeMap https://docs.rs/serde/*/serde/ser/trait.SerializeMap.html and FlatMapSerializer.

但是,请注意,鉴于它没有记录,那么任何未来版本的 serdecould如果您正在使用,请破坏您的代码FlatMapSerializer直接地。

use serde::{private::ser::FlatMapSerializer, ser::SerializeMap, Serializer};

impl Serialize for Message {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut s = serializer.serialize_map(None)?;

        let type_ = &match self {
            Message::T1(_) => 1,
            Message::T2(_) => 2,
            Message::T3(_) => 3,
        };
        s.serialize_entry("type", &type_)?;

        match self {
            Message::T1(t) => t.serialize(FlatMapSerializer(&mut s))?,
            Message::T2(t) => t.serialize(FlatMapSerializer(&mut s))?,
            Message::T3(t) => t.serialize(FlatMapSerializer(&mut s))?,
        }

        s.end()
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 serde 通过数值作为类型标识符对 json 进行反序列化 的相关文章

随机推荐

  • 为什么我在 iOS 设备测试中收到“Building MacinTalk voice for asset: (null)”

    我正在设备上执行以下文本转语音项目代码 但收到错误 为资产构建 MacinTalk 语音 null 请帮助我解决问题或提示出现了什么问题 AVSpeechSynthesizer synthesizer AVSpeechSynthesizer
  • 在 Angular 中使用多个模块有什么好处?

    我是 Angular Js 的熟悉者 最近我发现在一些项目中 在主模块中创建并组装了多个 Angular 模块 代码看起来像 angular module main main sub1 main sub2 main sub2 angular
  • 将 Mailjet API v3 包装器集成为 Codeigniter 库

    我怎样才能整合Mailjet API PHP 包装器 https github com mailjet mailjet apiv3 php no composer作为库安装到我的 Codeigniter 安装中 是不是把内容放进去那么简单存
  • 如何控制 Pepper QiSDK 上的 LED?

    On NAOqi框架2 5有一个模块可以让你控制机器人的 LED 灯ALLeds 有类似的东西可以让我控制 Pepper 的 LED 灯吗 不 没有 截至 2020 年 1 月 在 Pepper QiSDK 中 LED 状态自动取决于 Pe
  • Xcode 6.1 Swift 扩展 - SourceKit 服务崩溃

    我正在尝试向 Swift 添加扩展 以便我可以格式化双精度数 如在此答案中找到的那样 Swift 中的精确字符串格式说明符 https stackoverflow com questions 24051314 precision strin
  • Angular:如何从 HttpClient 下载文件?

    我需要从我的后端下载一个Excel 它返回一个文件 当我执行请求时 我收到错误 类型错误 您在需要流的地方提供了 未定义 你 可以提供 Observable Promise Array 或 Iterable 我的代码是 this http
  • 快速查看生成器 iOS

    有没有办法扩展 iOS 上的快速查找框架来处理未知的文件类型 就像在 Mac 上一样 我不想切换到我的应用程序来预览文件 就像在电子邮件或 iMessage 中查看图像文件一样 我想删除必须选择使用哪个应用程序来打开文件的步骤 在 Mac
  • Oracle 10 中的本地临时表(适用于存储过程的范围)

    我是甲骨文新手 我需要在存储过程中处理大量数据 我正在考虑使用临时表 我正在使用连接池 并且该应用程序是多线程的 有没有一种方法可以为每次调用存储过程创建不同的表实例来创建临时表 以便来自多个存储过程调用的数据不会混淆 你说你是 Oracl
  • Visual Studio 代码调试器未连接到 SAM 本地

    根据 AWS 文档 我将像这样启动本地 SAM sam local start api d 5858 我的 launch json 中有以下内容 version 0 2 0 configurations name Attach to SAM
  • 如何更改“go build”的库路径

    我正在尝试与 goncurses 一起工作 在 Centos 6 上 ncurses 库很旧 5 7 想要 5 9 所以我从源代码构建了 ncurses 并将其安装到 usr lib usr include 等中 如何告诉 go get 针
  • 使用异步收集 Publisher 值

    我一直在为我们拥有的一些组合代码编写一些单元测试 我遇到了一些问题 我想我已经简化了这个测试中的各个部分 注意 这不是一个测试 这是我试图理解为什么其中一个测试不起作用 func test collectingPassthroughValu
  • 如何以 Express 方式传输响应?

    我一直在尝试让一个快速应用程序以流形式发送响应 var Readable require stream Readable var rs Readable app get report function req res res statusC
  • VideoView SeekTo 在不同设备上工作异常 - Android

    我在视频视图上使用此代码来寻求自定义位置 但它在三星设备上工作正常 但在索尼设备上 视频寻求开始位置 开始 我想让视频回到上次暂停的位置 创建视频视图 VideoView mVideoPlayer setVideoPath ViDpath
  • RS 232 中断信号

    我有一个RS232信号捕获设备 而且效果很好 我需要一些帮助来理解数据 基本上我们购买它是因为我们正在处理 80 年代末使用串行通信的机器控制器 尽管知道端口参数 但我们运气不佳 从我转储的数据来看 机器控制正在使用中断信号作为其协议的一部
  • 如何禁用 django-rest-framework 的管理风格可浏览界面?

    我在用django rest framework http django rest framework org 它提供了一个很棒的 Django 管理风格的可浏览的自文档 API 但任何人都可以访问这些页面并使用该界面添加数据 POST 我
  • 如何使用 Material-ui@next TextField 错误道具

    我想使用 Material UI Next 文本字段error props link https material ui next com api text field textfield 道具类型为boolean 之前版本的 Materi
  • 将日期添加到日历热图 R

    I m plotting a calender heat map using Paul Bleicher s calenderHeat R code https raw githubusercontent com iascchen VisH
  • 我可以使用 DataContract 序列化程序序列化 Dictionary 吗?

    我计划构建一个 WCF 服务 返回序列化为 JSON 的通用字典对象 不幸的是 序列化失败 因为对象可能总是不同的 KnownTypes 没有帮助 因为属性类型是 Dictionary 而且我不能说 KnownType 因为类可能总是不同
  • 在 .NET 中可以将数组或列表作为属性返回吗?

    我正在阅读 MSDN 上的一些文档 了解有关某些内容是否应该作为属性或方法实现的注意事项 我特别遇到了一条规则 对此我有疑问 如果 操作返回一个数组 请使用方法 而不是属性 页面在这里 在属性和方法之间进行选择 https msdn mic
  • 使用 serde 通过数值作为类型标识符对 json 进行反序列化

    我对 Rust 很陌生 并且有 OOP 背景 所以 也许我误解了一些 Rust 基础知识 我想用 serde 解析固定的 json 结构 该结构代表不同的消息类型之一 每条消息都有一个数字type属性来区分它 各个消息类型的确切结构大多不同