Rust Serde - 反序列化时展平路径

12
我想将一个相当深的 JSON 反序列化为 Rust 结构体:
{
  "root": {
    "f1": {
      "f2": {
         "f3": 123
       }
    }
  }
}

在实现Deserialize时,我必须创建太多的结构体——对于上述JSON中的每个层级都要创建一个结构体。
struct Root {
  f1: Field1
}
struct Field1 {
  f2: Field2
}
struct Field3 {
  f3: Field3
}
// ...

有没有什么方法可以避免使用这么多的结构体?我没有找到任何有用的属性来帮助衍生出新的结构体。我想要像下面这样的东西:

struct Root {
  // some attr?
  f3: u64
}

当然,可以实现自定义反序列化,但我想知道是否有默认方法来实现这一点。


我认为没有内置的功能。请参阅此旧问题,它引用了#119,但那是 serde(flatten),据我所知,只能反向工作(尽管有可能曾经讨论过嵌套json的展平)。 - Masklinn
3
如果您真的不想创建结构体,可以使用无类型值,并手动提取嵌套值。 - Sven Marnach
你想要全部数据吗?还是只需要f3中的数据?如果是后者,那么类似于XPath的JSON解析更为合适。 - JGFMK
1
好奇心驱使,这里是自定义反序列化器的样子。 - Caesar
宏可以帮助减少样板代码 :) - Rom's
虽然并非技术上的解决方案,但 json_dotpath crate 可以让生活变得更加轻松。 - cyqsimon
1个回答

1

我认为这是一个有趣的问题/挑战,所以我编写了一个简单的过程宏属性来实现这个功能,它被称为serde_flat_path。以下是一个示例,展示了如何使用它来提供问题描述中所述的功能:

#[serde_flat_path::flat_path]
#[derive(Serialize, Deserialize)]
struct Root {
    #[flat_path(path = ["f1", "f2", "f3"])]
    f3: u64,
}

这个属性必须放在派生SerializeDeserialize之前,因为它会在带有#[flat_path(...)]的字段上放置serde属性。我已经尽力确保这个属性与其他serde属性和辅助crate兼容。它也可以用于像下面这样更复杂的类型。对于SerializerDeserializer来说,它应该看起来与实际编写链中所有结构体没有区别。有关详细信息,您可以查看crate的自述文件。

#[serde_flat_path::flat_path]
#[derive(Serialize, Deserialize)]
#[serde(tag = "foobar")]
pub enum Bar {
    Foo {
        #[flat_path(path = ["a", "b", "c", "d", "e"])]
        #[serde(with = "flip_bool")]
        foo: bool,
        #[flat_path(path = ["x", "y", "z"])]
        #[serde(skip_serializing_if = "Option::is_some")]
        x: Option<u64>,
    },
    Bar {
        #[flat_path(path = ["a", "b"])]
        bar: Bar,
    },
    Baz,
    Biz {
        #[serde(with = "add_one")]
        z: f64,
    }
}

不过需要提醒的是,这个过程宏并不完美。目前它无法处理由于宏展开方式而产生的重叠扁平路径。如果尝试这样做,除非使用allow_overlap功能,否则会在编译时发出错误。有时它也会在泛型方面遇到困难,但我正在努力改进。

// This would produce an error since x and y have overlapping paths
#[serde_flat_path::flat_path]
#[derive(Serialize, Deserialize)]
struct Foo {
    z: bool,
    #[flat_path(path = ["a", "b", "x"])]
    x: u64,
    #[flat_path(path = ["a", "c", "y"])]
    y: u64,
}

let foo = Foo { z: true, x: 123, y: 456 };
println!("{}", serde_json::to_string(&foo).unwrap());
// Output:
// {"z":true,"a":{"b":{"x":123}},"a":{"c":{"y":456}}}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接