反序列化“dyn T”结构的 JSON 列表?

Deserialize JSON list of `dyn T` structs?

提问人:lmseper 提问时间:6/17/2023 更新时间:6/17/2023 访问量:80

问:

我正在尝试学习 Rust,并且一直在构建一个光线追踪器作为初学者项目,以弄脏我的语言。我按照 Peter Shirley 的《One Weekend 中的光线追踪》构建了我的光线追踪器 这本书是用 C++ 编写的,我已经尽力将其移植到 Rust 中,诚然,代码可能不是以类似 Rust 的方式编写的。

我已经让光线追踪器工作了,我目前正在尝试实现一个场景保存系统,您可以在其中将场景保存为 JSON 文件,其中包含有关场景相机、对象等的各种信息。

在使用反序列化场景的 JSON 以便将其加载到程序中时,我遇到了一个问题。我在一个简单的 Python 脚本中实现了,其输出如下所示,保存为:serdescene.json

{
    "aspect_ratio": 1.5,
    "image_width": 1200,
    "image_height": 800,
    "samples_per_pixel": 500,
    "camera": {
        "origin": {
            "vec": [
                13.0,
                2.0,
                3.0
            ]
        },
        "lower_left_corner": {
            "vec": [
                3.0112371,
                -1.1992972,
                2.6938112
            ]
        },
        "horizontal": {
            "vec": [
                1.5556582,
                0,
                -5.055889
            ]
        },
        "vertical": {
            "vec": [
                -0.49034908,
                3.4890225,
                -0.15087664
            ]
        },
        "u": {
            "vec": [
                0.29408586,
                0,
                -0.955779
            ]
        },
        "v": {
            "vec": [
                -0.13904539,
                0.9893614,
                -0.042783197
            ]
        },
        "w": {
            "vec": [
                0.9456108,
                0.14547859,
                0.29095718
            ]
        },
        "radius": 0.5
    },
    "world": [
        {
            "center": {
                "vec": [
                    0,
                    -1000,
                    0
                ]
            },
            "r": 1000,
            "material": {
                "type": "lambertian",
                "albedo": [
                    0.5,
                    0.5,
                    0.5
                ]
            }
        },
        {
            "center": {
                "vec": [
                    0.3712137,
                    0.2,
                    1.5109301
                ]
            },
            "r": 0.2,
            "material": {
                "type": "metal",
                "albedo": [
                    0.66978514,
                    0.9735459,
                    0.52093863
                ],
                "fuzziness": 0.045185298
            }
        },
        .
        .
        .
    ]
}

目前我的光线追踪器中只有球体,所以两个 s 之间唯一不同的是 ,我有 、 和,并定义如下:Renderable"world""material"LambertianMaterialMetalDielectric

pub struct LambertianMaterial { 
    albedo: Color
}

pub struct Metal {
    albedo: Color,
    fuzziness: f32
}

pub struct Dielectric {
    // index of refraction (𝜂')
    ir: f32
}

对元数据(如 和 、 等)进行去整理是完全可以的。我的问题是这个字段,因为它是一个列表,其中包含所有使用该特征的各种领域,我将其定义为:"camera""aspect_ratio""image_width"world"Renderable

pub trait Renderable: fmt::Display {
    fn hit (&self, ray: &Ray, t_min: f32, t_max: f32) -> (bool, HitRecord);
}

在光线追踪器中,这些球体通过循环访问另一个包含 s 的结构体来渲染,称为 ,定义为:vecRenderableRenderableList

pub struct RenderableList {
    objects: Vec<Rc<dyn Renderable>>
}

然而,尝试使用 serde 从 JSON 反序列化 a 被证明是困难的。简单地在结构定义上打一个显然并不能解决它。RenderableList#[derive(Debug, Deserialize)]

我尝试通读这篇关于序列化 Arc<Mutex> 的文章,但它更多的是在序列化方面,而不是反序列化方面。

我尝试通读有关实现反序列化器的 serde 文档,但我认为这比我实际需要的更复杂和矫枉过正。

我最初的想法是做以下事情:

#[derive(Deserialize, Debug)]
struct Scene {
    aspect_ratio: f32,
    image_width: i32,
    image_height: i32,
    samples_per_pixel: i32,
    camera: Camera,
    world: RenderableList,
}

fn load_scene<P: AsRef<Path>>(path: P) -> Result<Scene, Box<dyn Error>> {
    // Open the file in read-only mode with buffer.
    let file = File::open(path)?;
    let reader = BufReader::new(file);

    // Read the JSON contents of the file as an instance of `Scene`.
    let s = serde_json::from_reader(reader)?;

    // Return the `Scene`.
    Ok(s)
}

但如前所述,当尝试使用 进行反序列化时,这会产生错误。具体来说,我得到:RenderableList#[derive(Debug, Deserialize)]

the trait bound `Rc<dyn Renderable>: Deserialize<'_>` is not satisfied
the following other types implement trait `Deserialize<'de>`:
  &'a Path
  &'a [u8]
  &'a str
  ()
  (T0, T1)
  (T0, T1, T2)
  (T0, T1, T2, T3)
  (T0, T1, T2, T3, T4)
and 131 others
required for `Vec<Rc<dyn Renderable>>` to implement `Deserialize<'_>`

有人碰巧知道如何解决这个问题吗?

rust 反序列化 serde serde-json

评论

0赞 apetranzilla 6/17/2023
你试过typetag了吗?这似乎是它设计的确切用例。

答:

1赞 drewtato 6/17/2023 #1

解决此问题的最直接方法是使用枚举而不是特征对象。

#[derive(Debug, Deserialize)]
#[serde(untagged)] // since your current JSON is untagged
pub enum Object {
    Circle(Circle),
}

#[derive(Debug, Deserialize)]
pub struct RenderableList {
    objects: Vec<Rc<Object>>
}

impl Renderable for Object {
    fn hit (&self, ray: &Ray, t_min: f32, t_max: f32) -> (bool, HitRecord) {
        match self {
            Object::Circle(c) => c.hit(ray, t_min, t_max)
        }
    }
}

现在,当您添加新对象时,您将:

  • 为该对象创建一个类型。
  • 将多属性添加到Object
  • 为对象实现。Renderable
  • 向匹配表达式添加分支。

您可以对材料执行相同的操作。

实际上没有一种方法可以检索特征的所有实现者的列表,您需要这样做才能反序列化特征对象。也没有内置的方法来检索枚举变体列表,但 serde 派生宏可以处理它。枚举通常也比特征对象快。

我确实找到了这个可以对特征对象执行此操作的板条箱,但我还没有尝试过:typetag

另请参阅有关枚举的 serde 指南章节

评论

0赞 lmseper 6/18/2023
哦,有意思!我不知道您可以在 Rust 中实现枚举的特征。太酷了!Typetag 看起来也像我的确切用例,所以我将尝试这两种解决方案。非常感谢,我会报告我的结果!