Reputation: 197
I am wondering how one could support a "type-fluid" (aka variant type) field in JSON when mapped via serde. As parser, I am using json5
crate (backed by serde
). And certain input fields might contain key-values which are either "foo": "bar"
or a list behind the same key, i.e. "foo": [ "bar", "and", "more" ]
.
And the documentation of https://serde.rs/data-model.html does not actually give me a clue on how to map this usecase. I tried using an enum as variant carrier, see below, but that does not work, and at runtime this ends up in some error result like Err(Message { msg: "unknown variant `hello world`, expected `Single` or `Multi`", location: Some(Location { line: 4, column: 20 }) })
.
So far I still don't see a way to tell it "take this either as value or as list of values and let me see which kind you have parsed".
#[derive(Deserialize, Debug, PartialEq)]
enum StringOrStrings {
Single(String),
Multi(Vec<String>),
}
#[derive(Deserialize, Debug, PartialEq)]
struct Config {
message: StringOrStrings,
n: i32,
}
Upvotes: 1
Views: 143
Reputation: 169018
You can use an untagged enum as the other answer explains, which allows you to deserialize one out of a few options, trying each in order until one succeeds.
However, you can generalize this solution even further to handle any deserializable data type (not just strings), as well as hide the "maybe Vec
" detail by wrapping a single value in a Vec
like this:
use serde::{Deserialize, Deserializer};
#[derive(Deserialize, Debug)]
struct Config {
#[serde(deserialize_with = "deser_maybe_vec")]
message: Vec<String>,
n: i32,
}
fn deser_maybe_vec<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum SingleOrVec<T> {
Single(T),
Vec(Vec<T>),
}
Ok(match SingleOrVec::deserialize(deserializer)? {
SingleOrVec::Single(v) => vec![v],
SingleOrVec::Vec(v) => v,
})
}
Note that if you need to derive Serialize
on Config
you don't need to handle that case specially using serialize_with
because the default implementation will produce a sequence containing one item, which is a valid representation of the data.
Upvotes: 1
Reputation: 1476
Here you go:
Basically the same answer as here
extern crate serde;
extern crate serde_json;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum StringOrStringVec {
String(String),
Vec(Vec<String>)
}
#[derive(Deserialize, Debug)]
struct Config {
message: StringOrStringVec,
n: i32,
}
fn main (){
let json_string = r#"
{
"message": "hello",
"n": 42
}
"#;
let json_vec = r#"
{
"message": ["hello", "world"],
"n": 42
}
"#;
let my_obj: Config = serde_json::from_str(json_string).unwrap();
println!("{:?}", my_obj);
let my_obj: Config = serde_json::from_str(json_vec).unwrap();
println!("{:?}", my_obj);
}
Upvotes: 2