如何使用Serde的自定义(反)序列化来更新任意输入的子集?

时间:2018-06-23 15:12:51

标签: rust serde

我需要更新任意输入文件的特定字段,而无需触摸程序不知道的任何键或值。

这是示例输入文件:

{ 
  "alpha": {
    "a": 1,
    "z": 2
  },
  "beta": "b"
}

我想将alpha.a更新100:

{ 
  "alpha": {
    "a": 101,
    "z": 2
  },
  "beta": "b"
}

可以使用serde_json::Valuetoml::value::Value之类的类型来执行此操作,但是此代码非常麻烦:

extern crate serde; // 1.0.66
extern crate serde_json; // 1.0.21

use serde_json::Value;

fn main() {
    let input = r#"{ 
      "alpha": {
        "a": 1,
        "z": 2
      },
      "beta": "b"
    }"#;

    let mut to_change: Value = serde_json::from_str(input).unwrap();
    {
        let obj = to_change.as_object_mut().unwrap();
        let alpha = obj.get_mut("alpha").unwrap();

        let obj = alpha.as_object_mut().unwrap();
        let num = {
            let a = obj.get("a").unwrap();

            let mut num = a.as_i64().unwrap();
            num += 100;
            num
        };
        obj.insert("a".into(), Value::Number(num.into()));
    }
    println!("{}", serde_json::to_string_pretty(&to_change).unwrap());
}

我宁愿使用干净的派生语法:

extern crate serde; // 1.0.66
#[macro_use]
extern crate serde_derive; // 1.0.66
extern crate serde_json; // 1.0.21

#[derive(Debug, Deserialize, Serialize)]
struct WhatICareAbout {
    alpha: Alpha,
}

#[derive(Debug, Deserialize, Serialize)]
struct Alpha {
    a: i32,
}

fn main() {
    let input = r#"{ 
          "alpha": {
            "a": 1,
            "z": 2
          },
          "beta": "b"
        }"#;

    let mut subobject: WhatICareAbout = serde_json::from_str(input).unwrap();
    subobject.alpha.a += 1;
    println!("{}", serde_json::to_string_pretty(&subobject).unwrap());
}

这会运行,但是会清除所有未知密钥:

{
  "alpha": {
    "a": 2
  }
}

有没有一种方法可以使用漂亮的DeserializeSerialize实现,同时仍然保留我不知道的键和值?

一个理想的答案是:

  • 适用于大多数Serde格式-我在这里显示JSON,但我的真实代码是TOML。
  • 允许添加,更新和删除字段。

1 个答案:

答案 0 :(得分:2)

As of Serde 1.0.34,您可以使用#[serde(flatten)]捕获所有不需要的键和值,以创建“ catch-all”字段:

extern crate serde; // 1.0.66
#[macro_use]
extern crate serde_derive; // 1.0.66
extern crate serde_json; // 1.0.21

type Other = serde_json::Map<String, serde_json::Value>;

#[derive(Debug, Deserialize, Serialize)]
struct WhatICareAbout {
    alpha: Alpha,
    #[serde(flatten)]
    other: Other,
}

#[derive(Debug, Deserialize, Serialize)]
struct Alpha {
    a: i32,
    #[serde(flatten)]
    other: Other,
}

对于TOML,您可以使用Other的并行定义:

type Other = std::collections::BTreeMap<String, Value>;
  

我是否正确假设键/格式的顺序将被这种方法完全丢弃? (至少,我希望alpha首先被序列化,而不管其最初处于什么位置)

是的,输出键的顺序将基于结构定义中字段顺序和您选择的映射类型的组合。上面使用的是BTreeMap,因此“其他”键将按字母顺序排列,但都位于特定字段之后。

您可以选择使用IndexMap来保留“其他”键的顺序,尽管它们仍然相对于您添加的特定字段:

extern crate indexmap; // 0.4.1 + features = ["serde-1"]

type Other = indexmap::IndexMap<String, serde_json::Value>;

另请参阅: