#serde-derive #derive-deserialize #serde #json #bincode #macro-derive #deserializaion

serde-split

为不同目的派生两个 serde 特性的实现

4 个版本

0.1.3 2023年10月2日
0.1.2 2023年10月2日
0.1.1 2023年9月26日
0.1.0 2023年9月26日

#1319编码

MIT/Apache

14KB
187 代码行

serde-split

serde-split 是一个围绕 serde's 派生宏的封装,用于序列化和反序列化。

使用 serde-splitDeserializeSerialize 派生宏将允许派生器为支持跳过字段(如 serde_json)和不支持跳过字段(如 bincode)的序列化器派生两个独立的 trait 实现。

示例

假设你正在使用 bevy 开发一个游戏,并且有一个动画格式。在开发过程中,你可能希望这种动画格式是易于修改的 JSON 数据,但在正式发布时,你希望将其打包为二进制数据。

对于 JSON 开发资源,你将从 JSON 文件的相对路径加载每个关键帧,而对于发布资源,你将从二进制文件反序列化精灵图作为 PNG 数据。

在这个启发了这个 crate 的真实用例中,你可以这样实现

use serde_split::{Deserialize, Serialize};
use image::RgbaImage;

mod rgba_image {
    /* PNG serde impl */
}

fn default_image() -> RgbaImage {
    RgbaImage::new(1, 1)
}

#[derive(Deserialize, Serialize)]
pub struct SpriteAnimation {
    #[json(skip, default = "default_image")]
    #[bin(with = "rgba_image")]
    pub sprite_sheet: RgbaImage,

    pub keyframes: Vec<KeyFrame>,
}

bincode 也不支持相邻的枚举标记,这可以帮助简化人类维护的 JSON 数据,但会破坏二进制格式。

你可以想象,对于同一款游戏,你可能有一些可以具有静态位置或基于路径的位置的碰撞对象

use bevy::prelude::Vec2;
use serde::{Deserialize, Serialize};

// This is the only way to declare this while allowing it to work with bincode
#[derive(Deserialize, Serialize)]
pub enum ObjectPosition {
    Static(Vec2),
    Path {
        start: Vec2,
        points: Vec<Vec2>
    }
}

不幸的是,这会使我们的 JSON 数据看起来像这样

{
    "object": {
        "position": {
            "Static": [0.0, 0.0]
        }
    },
    "object2": {
        "position": {
            "Path": {
                "start": [0.0, 0.0],
                "points": [
                    [10.0, 10.0],
                    [-10.0, 10.0],
                    [0.0, 0.0]
                ]
            }
        }
    }
}

使用 serde-split 的宏,你可以这样声明你的结构体

use bevy::prelude::Vec2;
use serde_split::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
#[json(untagged)]
pub enum ObjectPosition {
    Static(Vec2),
    Path {
        start: Vec2,
        points: Vec<Vec2>
    }
}

现在你的 JSON 表示形式得到了简化

{
    "object": {
        "position": [0.0, 0.0]
    },
    "object2": {
        "position": {
            "start": [0.0, 0.0],
            "points": [
                [10.0, 10.0],
                [-10.0, 10.0],
                [0.0, 0.0]
            ]
        }
    }
}

而二进制表示形式对于 bincode 的(反)序列化器来说定义良好!

它会展开成什么样子?

如果我们使用上面提到的精灵动画示例,以下结构体

#[derive(Deserialize)]
pub struct SpriteAnimation {
    #[json(skip, default = "default_image")]
    #[bin(with = "rgba_image")]
    pub sprite_sheet: RgbaImage,

    pub keyframes: Vec<KeyFrame>,
}

将大致展开为

const _: () = {
    #[derive(serde::Deserialize)]
    #[serde(remote = "SpriteAnimation")]
    pub struct SpriteAnimationJsonImpl {
        #[serde(skip, default = "default_image")]
        pub sprite_sheet: RgbaImage,
        pub keyframes: Vec<KeyFrame>
    }

    #[derive(serde::Deserialize)]
    #[serde(remote = "SpriteAnimation")]
    pub struct SpriteAnimationBinaryImpl {
        #[serde(with = "rgba_image")]
        pub sprite_sheet: RgbaImage,
        pub keyframes: Vec<KeyFrame>
    }

    impl<'de> serde::Deserialize<'de> for SpriteAnimation {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: serde::Deserializer<'de>
        {
            if deserializer.is_human_readable() {
                SpriteAnimationJsonImpl::deserialize(deserializer)
            } else {
                SpriteAnimationBinaryImpl::deserialize(deserializer)
            }
        }
    }
};

对于 Serialize 也会有类似的展开。

依赖项

~3MB
~57K SLoC