I use this data structure in a project:
#[derive(Serialize, Deserialize)]
pub enum Field {
last_name(String),
first_name(String),
/* etc. */
}
#[derive(Serialize, Deserialize)]
pub struct Update {
pub id: Id,
pub field: Field,
}
The enum is not really useful by itself, I use it for deserialization of JSON. So is it possible to do something like that?
#[derive(Serialize, Deserialize)]
pub struct PersonUpdate {
pub id: Id,
pub field: enum {
last_name(String),
first_name(String),
}
}
It is not possible, you must give it a name, like you did in the first example.
Related
I know that there is a similar question here, but I've not been able to make it fit my use case.
I have a Model struct that's nested into other structs. The model can have two different types of Config objects, a ModelConfig or a SeedConfig. They are nearly identical save for a few fields. As it stands now, I need two concrete implementations of Model (SeedModel and ModelModel) in order to change the config field, resulting in duplication of all the methods and trait implementations.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct MetaModel {
pub model: Model
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Model {
pub name: String,
pub config: Option<ModelConfig>
}
What I've tried:
Using Generics: This pushes the generic type up the chain and results in very complex definitions and areas where I don't have the context to create the parent struct (i.e. the MetaModel definition has no access to the Model definition at creation).
This eventually results in a the type parameter C is not constrained by the impl trait, self type, or predicates unconstrained type parameter error
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct MetaModel<C> {
pub model: Model<C>
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Model<C> {
pub name: String,
pub config: Option<C>
}
Trait Objects: This doesn't work because serde cannot serialize trait objects
pub trait Config {}
pub struct ModelConfig;
impl Config for ModelConfig {}
pub struct SeedConfig;
impl Config for SeedConfig {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Model {
pub name: String,
pub config: Option<Box<dyn Config>
}
What I'd like to do:
impl OtherTrait for Model {
type Value = Model;
fn build_model(&self, m: DerivedMeta) -> Result<Self::Value, &'static str> {
Ok(Model {
// Either a SeedConfig or a ModelConfig
})
}
}
What I would do is use a combination of #[serde(flatten)] and #[serde(untagged)]:
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
struct Config {
members: i32,
shared_by: String,
both: i64,
#[serde(flatten)]
specific: SpecificConfig,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(untagged)]
enum SpecificConfig {
SeedConfig {
some_members: i16,
unique_to: String,
seed_config: u64,
},
ModelConfig {
other_members: i8,
not_shared_with_above: u32,
},
}
Serde explanation of flatten:
Flatten the contents of this field into the container it is defined in.
This removes one level of structure between the serialized representation and the Rust data structure representation.
Serde explanation of untagged:
There is no explicit tag identifying which variant the data contains. Serde will try to match the data against each variant in order and the first one that deserializes successfully is the one returned.
By combining these two, we get the following behavior:
flatten allows all shared fields and specific fields to be on the same level in the config
untagged allows us to avoid adding an explicit tag in the config
all shared properties are directly accessible
only specific properties require matching the specific enum
I have two structs that I want to serialize/deserialize with the tag as a "type" field in JSON, like so.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
struct ThingA {
value: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
struct ThingB {
value: usize,
}
These serialize as expected. For example,
let a = ThingA { value: 0 };
println!("{}", serde_json::to_string(&a)?);
// This yields the expected result:
// {"type":"ThingA","value":0}
However, I'm running into trouble when I try to add an enum to stand in as a union type for the structs.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
enum Thing {
ThingA(ThingA),
ThingB(ThingB),
}
The definition above works fine for deserializing JSON, but adds an extra field during serialization.
let json = r#"{"type": "ThingB", "value": 0}"#;
let thing: Thing = serde_json::from_str(json)?;
// Correctly parses to:
// ThingB(ThingB { value: 0 })
println!("{}", serde_json::to_string(&thing)?);
// Incorrectly serializes with an extra "type" field:
// {"type":"ThingB","type":"ThingB","value":0}
Changing #[serde(tag = "type")] to #[serde(untagged)] on the Thing enum causes the opposite problem: Thing instances serialize properly, but don't get parsed correctly anymore.
My goal is to get JSON {"type": "ThingB", value: 0} to evaluate to Thing::ThingB(ThingB {value: 0}) during deserialization, and vice versa, but only if I'm deserializing to a Thing. If I have an unwrapped ThingB, like ThingB {value: 0}, I want it to serialize to {"type": "ThingB", value: 0} as well.
So my questions are: Is there any way to assign the serde tag or untagged attributes such that they only apply during serialization/deserialization (similar to serde's rename)? If not, any advice on how to implement Serialize and/or Deserialize to achieve my goal?
You can just use tag in your Thing enum, leaving the others clean:
use serde::{Serialize, Deserialize}; // 1.0.124
use serde_json; // 1.0.64
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ThingA {
value: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ThingB {
value: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
enum Thing {
ThingA(ThingA),
ThingB(ThingB),
}
fn main() {
let json = r#"{"type": "ThingB", "value": 0}"#;
let thing: Thing = serde_json::from_str(json).unwrap();
println!("{}", serde_json::to_string(&thing).unwrap());
}
Playground
As requested in the comments.
In case we would want to have both tagged (enum and structs) we would need to make some serde abracadabra playing with wrappers and with the with. More info can be found here
I use Serde to deserialize a custom configuration file written in YAML. The file can contain definitions of various kinds that I represent as internally tagged enums:
OfKindFoo:
kind: Foo
bar: bar;
baz: baz;
OfKindQux:
kind: Qux
quux: qux;
I represent it in Rust like this:
#[derive(Deserialize)]
#[serde(tag = "kind")]
enum Definition {
Foo(Foo),
Qux(Qux),
}
#[derive(Deserialize)]
struct Foo {
bar: String,
baz: String,
}
#[derive(Deserialize)]
struct Qux {
quux: String,
}
I want the user to be able to omit the kind field completely, and when it is omitted Serde should default to deserializing it as Foo.
I started to implement Deserialize on Definition. I'm trying to deserialize it as a map and look for the kind key and return a respective enum variant based on this key and whether it is present.
I need to somehow "forward" the deserialization of other map fields to Foo::deserialize or Bar::deserialize, respectively. fn deserialize only takes one argument which is Deserializer. Is there a way to "convert" the map into a deserializer or otherwise get a deserializer that "starts" on that particular map?
I cannot use #[serde(other)] because it returns Err for a missing tag. Even if it didn't, the documentation states that other can only be applied to a "unit variant", a variant not containing any data.
You can mark the main enum as untagged and add tags to the sub-structs that do have a tag (this feature is not documented, but was added deliberately and so seems likely to stay). The variant without a tag should be declared after the other ones though, as serde will try to deserialize the variants in declared order with #[serde(untagged)]. Also note that if in your actual code, the variants and the structs have different names, or you're using #[serde(rename)], with this, the names of the structs are what matters for (de)serialization, not the variant names. All that applied to your example:
#[derive(Deserialize)]
#[serde(untagged)]
enum Definition {
Qux(Qux),
Foo(Foo), // variant that doesn't have a tag is the last one
}
#[derive(Deserialize)]
struct Foo {
bar: String,
baz: String,
}
#[derive(Deserialize)]
#[serde(tag = "kind")]
// if you want the tag to be "qux" instead of "Qux", do
// #[serde(rename = "qux")]
// here (or add `, rename = "qux"` to the previous serde attribute)
struct Qux {
quux: String,
}
If structs have the same shape or all the fields are optional, accepted answer won't work and will be deserialized to the first kind. playground
With the monostate crate it can be fixed.
use monostate::MustBe;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Action {
Hi(Hi),
Bye(Bye),
Unknown(Unknown),
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Hi {
kind: MustBe!("Hi"),
name: String,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Bye {
kind: MustBe!("Bye"),
name: String,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Unknown {
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tests() {
assert_eq!(
Action::Hi(Hi { kind: Default::default(), name: "John".to_string() }),
serde_json::from_str::<Action>(r#"{"kind": "Hi", "name": "John"}"#).unwrap()
);
assert_eq!(
Action::Bye(Bye { kind: Default::default(), name: "John".to_string() }),
serde_json::from_str::<Action>(r#"{ "kind": "Bye", "name": "John" }"#).unwrap()
);
assert_eq!(
Action::Unknown(Unknown { }),
serde_json::from_str::<Action>(r#"{ "name": "John" }"#).unwrap()
);
assert_eq!(
Action::Unknown(Unknown { }),
serde_json::from_str::<Action>(r#"{}"#).unwrap()
);
}
}
The serde_json::to_string() function will generate a string which may include null for an Option<T>, or 0 for a u32. This makes the output larger, so I want to ignore these sorts of values.
I want to simplify the JSON string output of the following structure:
use serde_derive::Serialize; // 1.0.82
#[derive(Serialize)]
pub struct WeightWithOptionGroup {
pub group: Option<String>,
pub proportion: u32,
}
When group is None and proportion is 0, the JSON string should be "{}"
Thanks for the answerHow do I change Serde's default implementation to return an empty object instead of null?, it can resolve Optionproblem, but for 0 there is none solution.
The link Skip serializing field give me the answer.
And the fixed code:
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Ord, PartialOrd, Eq)]
pub struct WeightWithOptionGroup {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub group: Option<String>,
#[serde(skip_serializing_if = "is_zero")]
#[serde(default)]
pub proportion: u32,
}
/// This is only used for serialize
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_zero(num: &u32) -> bool {
*num == 0
}
There's a couple of ways you could do this:
Mark each of the fields with a skip_serialising_if attribute to say when to skip them. This is much easier, but you'll have to remember to do it for every field.
Write your own Serde serialiser that does this custom JSON form. This is more work, but shouldn't be too bad, especially given you can still use the stock JSON deserialiser.
For those searching how to skip serialization for some enum entries you can do this
#[derive(Serialize, Deserialize)]
enum Metadata<'a> {
App, // want this serialized
Ebook, // want this serialized
Empty // dont want this serialized
}
#[derive(Serialize, Deserialize)]
struct Request<'a> {
request_id: &'a str,
item_type: ItemType,
#[serde(skip_serializing_if = "metadata_is_empty")]
metadata: Metadata<'a>,
}
fn metadata_is_empty<'a>(metadata: &Metadata<'a>) -> bool {
match metadata {
Metadata::Empty => true,
_ => false
}
}
Given the struct:
#[derive(Debug, Serialize)]
pub struct ReqMetrics {
start: Timespec,
pub name: String
}
How can I ensure that Serde ignores the field start when serializing?
The correct annotation is #[serde(skip_serializing)].