让 prost 生成的 Rust 代码支持 Serialize/Deserialize 到 JSON

Telegram Bot API 返回的都是 JSON 数据,比如 访问 https://api.telegram.org/bot123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11/getMe 返回一个 User

我想把这个 JSON 转换成 Rust 中的结构,方便后续操作,这个可以利用 serde 很方便地做到


use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let point = Point { x: 1, y: 2 };

    // Convert the Point to a JSON string.
    let serialized = serde_json::to_string(&point).unwrap();

    // Prints serialized = {"x":1,"y":2}
    println!("serialized = {}", serialized);

    // Convert the JSON string back to a Point.
    let deserialized: Point = serde_json::from_str(&serialized).unwrap();

    // Prints deserialized = Point { x: 1, y: 2 }
    println!("deserialized = {:?}", deserialized);
}

问题是手动定义一个个 struct 太麻烦,那么可以用 macro 来帮我生成这些长得差不多的 struct 代码,但是仍然需要以另一种格式先定义好这些 JSON,比如:


{
    "struct": "User",
    "fields": {
        "id": {
            "type": "integer64",
            "optional": false
        },
        "is_bot": {
            "type": "boolean",
            "optional": false
        },
        "first_name": {
            "type": "string",
            "optional": false
        },
        "last_name": {
            "type": "string",
            "optional": true
        }
    }
}

然后通过解析这段 JSON,再通过 macro 生成我想要的各个 struct。问题是写这么一份 JSON 仍然很麻烦。有没有一种更直观的写法?

有,protobuf !


telegram.proto

syntax = "proto3";

package telegram;

message User {
    int64 id = 1;
    bool is_bot = 2;
    string first_name = 3;
    optional string last_name = 4;
    optional string username = 5;
    optional string language_code = 6;
}

相当直观了

现在只需要让 protobuf 生成的代码,可以支持 serde::Serialize 与 serde::Deserialize 即可

搜索一下 prost-build 有没有相应的支持

Config 里有个 type_attribute 方法恰好可以满足需求

最终相关代码大概如此:


build.rs

use std::io::Result;

fn main() -> Result<()> {
    let mut config = prost_build::Config::new();
    config.out_dir("src/proto");
    config.type_attribute(".", "#[derive(serde::Serialize,serde::Deserialize)]");
    config.compile_protos(&["src/proto/telegram.proto"], &["src/proto"])?;

    Ok(())
}

对应生成的代码:


telegram.rs

#[derive(serde::Serialize,serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct User {
    #[prost(int64, tag="1")]
    pub id: i64,
    #[prost(bool, tag="2")]
    pub is_bot: bool,
    #[prost(string, tag="3")]
    pub first_name: ::prost::alloc::string::String,
    #[prost(string, optional, tag="4")]
    pub last_name: ::core::option::Option<::prost::alloc::string::String>,
    #[prost(string, optional, tag="5")]
    pub username: ::core::option::Option<::prost::alloc::string::String>,
    #[prost(string, optional, tag="6")]
    pub language_code: ::core::option::Option<::prost::alloc::string::String>,
}

写个测试看下效果:


#[cfg(test)]
mod tests {
    use super::*;

    mod telegram {
        include!("proto/telegram.rs");
    }

    #[test]
    fn it_works() {

        let u = telegram::User::default();
        let s = serde_json::to_string(&u).unwrap();
        let u: telegram::User = serde_json::from_str(&s).unwrap();
        println!("s: {}", s);
        println!("u: {:#?}", u);
    }
}

输出:


s: {"id":0,"is_bot":false,"first_name":"","last_name":null,"username":null,"language_code":null}
u: User {
    id: 0,
    is_bot: false,
    first_name: "",
    last_name: None,
    username: None,
    language_code: None,
}

更进一步,可以在 build.rs 中直接抓取 https://core.telegram.org/bots/api 页面,然后解析其内容,转换为 protobuf,再生成 Rust 的 struct,一气呵成



posted on 2022-07-09 17:10  明天有风吹  阅读(1001)  评论(0编辑  收藏  举报

导航

+V atob('d2h5X251bGw=')

请备注:from博客园