问题
I need to update specific fields of an arbitrary input file without touching any keys or values that my program does not know about.
Here is an example input file:
{
"alpha": {
"a": 1,
"z": 2
},
"beta": "b"
}
I'd like to update alpha.a
by 100:
{
"alpha": {
"a": 101,
"z": 2
},
"beta": "b"
}
It is possible to do this with types like serde_json::Value and toml::value::Value, but this code is very cumbersome:
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());
}
I'd much rather use the clean derived syntax:
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());
}
This runs, but it strips out any unknown keys:
{
"alpha": {
"a": 2
}
}
Is there a way I can use the pretty Deserialize
and Serialize
implementations while still preserving keys and values I don't know about?
An ideal answer would:
- Work for most Serde formats — I'm showing JSON here but my real code is TOML.
- Allow adding, updating, and removing fields.
回答1:
As of Serde 1.0.34, you can use #[serde(flatten)] to capture all the unrecognized keys and values you don't care about to create a "catch-all" field:
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,
}
For TOML, you can use a parallel definition of Other
:
type Other = std::collections::BTreeMap<String, Value>;
Am I correct in assuming that the order of keys/formatting will be completely discarded by this approach? (At the very least, I expect
alpha
to be serialized first, regardless of the position it originally occupied)
Yes, the order of the output keys will be based on a combination of the field order in the struct definition and the type of map you choose. Above, a BTreeMap
is used, so the "other" keys will be alphabetized, but all come after the specific fields.
You could opt into an IndexMap
which would preserve the order of the "other" keys, although they'd still come relative to the specific fields you've added:
extern crate indexmap; // 0.4.1 + features = ["serde-1"]
type Other = indexmap::IndexMap<String, serde_json::Value>;
See also:
- How to (de)serialize a strongly typed JSON dictionary in Serde?
- Is it possible to flatten sub-object fields while parsing with serde_json?
- How to deserialize a subfield of a struct from the original struct's JSON with Serde?
来源:https://stackoverflow.com/questions/51002462/how-can-i-use-serdes-custom-deserialization-to-update-a-subset-of-arbitrary-i