YAML enum object representation - rust

I have this use case that I will explain in Rust. How do I represent equivalent in YAML
enum MainEnum {
Opt1(T1),
Opt2(T2)
}
struct T1 {
x: u32,
}
struct T2 {
y: bool
}
How do I represent this in YAML? I want YaML to either have the x field or y field depending on which Enum value is being selected in YAML.
This program does not run. Any ideas?
use serde_yaml;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct T1 {
x: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct T2 {
y: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
enum MainEnum {
Opt1(T1),
Opt2(T2),
}
fn main() {
let config = r#"
---
- Opt1
x: "true"
"#;
let me: MainEnum = serde_yaml::from_str(&config).unwrap();
println!("{:?}", me);
}
runtime error:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Scan(ScanError { mark: Marker { index: 17, line: 4, col: 5 }, info: "mapping values are not allowed in this context" })', src/main.rs:27:25

The first issue seems to be the YAML syntax you use. For a map in YAML you need to terminate the parent with a : so
let config = r#"
---
- Opt1:
x: true
"#;
would be actually correct YAML. But since you are asking serde_yaml for one instance of MainEnum, not for a list (a vec, for example) of MainEnum, it does not expect a YAML sequence but a single entry:
fn main() {
let config = r#"
---
Opt1:
x: true
"#;
let me: MainEnum = serde_yaml::from_str(&config).unwrap();
println!("{:?}", me);
}
This parses: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=029279e1e6baa77611c2a36e7d5bc3a4

Related

Deserialize struct which contains Weak

I have a struct which contains a weak ref to a pre-existing Arc. Is there a way for me to pass this Arc to the struct's deserializer, without writing my own deserializer/visitor?
Example struct / deserialize:
struct SomeGlobal;
#[derive(Deserialize)]
struct SomeState {
pub a: i32,
pub b: Weak<SomeGlobal>,
};
fn magic_deserialize(value: serde_json::Value, g: Arc<SomeGlobal>) -> Result<SomeState, ...> {
// what goes here?
}
#[test]
fn example() {
let g = Arc::new(SomeGlobal);
let value = json!({
"a": 5
});
let state = magic_deserialize(value, g.clone()).unwrap();
// state.a == 5
// state.b == weak ref to g
}
Since Weak<T> implements Default, you can simply annotate b with #[serde(skip)] to have serde skip serializing this field, and set it to default when deserializing.
#[derive(Debug, Serialize, Deserialize)]
struct SomeState {
pub a: i32,
#[serde(skip)]
pub b: Weak<SomeGlobal>,
}

How do you generate a struct dynamically at compile time in Rust?

I have the following:
struct Health {
health: f32,
}
struct Position {
position: Vec2,
}
struct Collections {
healths: Vec<Health>,
positions: Vec<Position>,
}
I would like to generate the Collections struct automatically; I am thinking using a macro?
I thought perhaps I could mark each struct I want to include with a custom attribute and then have a macro which builds the Collections struct.
How could I do this?
To be able to do something like custom attributes you need to write a proc_macro, that can do almost anything you need with your code.
For a simpler solution you may try with a normal macro_rules. For that you will need to enclose your type definitions into a macro that does the parsing, and emits back the type definition plus the extra code you need, in your case the Container class.
Something like this:
macro_rules! collectables {
(
$(
#[collection=$fname:ident]
$(#[$attr:meta])?
$vis:vis struct $name:ident $def:tt
)*
) => {
// The struct definitions
$(
$(#[$attr])?
$vis struct $name $def
)*
// The container
#[derive(Default, Debug)]
pub struct Collections {
$(
$fname: Vec<$name>,
)*
}
};
}
Now you can use the macro to build your original code (playground):
collectables!{
#[collection=healths]
#[derive(Debug)]
struct Health {
health: f32,
}
#[collection=positions]
#[derive(Debug)]
struct Position {
position: (f32, f32),
}
}
Note that as written the #[collection=xxx] attribute is mandatory and must be the first in every struct definition.
So I managed to solve this problem using a proc_macro. Each struct which is to be included in the final Storage struct is marked with the Component derive attribute. The Storage struct is then built with the storage!() macro.
use lazy_static::lazy_static;
use proc_macro::TokenStream;
use quote::quote;
use std::sync::Mutex;
use syn::{parse_macro_input, parse_str, DeriveInput, ExprType};
lazy_static! {
static ref COMPONENTS: Mutex<Vec<String>> = Mutex::new(Vec::new());
}
#[proc_macro_derive(Component)]
pub fn component(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput); ‣DeriveInput
let ident = input.ident; ‣Ident
COMPONENTS.lock().unwrap().push(ident.to_string());
let expanded = quote! { ‣TokenStream
impl component::Component for #ident {}
};
TokenStream::from(expanded)
}
#[proc_macro]
pub fn storage(_input: TokenStream) -> TokenStream {
println!("Building Storage with: {:?}", COMPONENTS.lock().unwrap());
let mut fields = Vec::new(); ‣Vec<ExprType>
for type_name in COMPONENTS.lock().unwrap().iter() { ‣&String
let field = parse_str::<ExprType>( ‣ExprType
format!("{}s: Vec<{}>", type_name.to_lowercase(), type_name).as_str(),
) ‣Result<ExprType, Error>
.expect("Could not parse component field type");
fields.push(field);
}
let expanded = quote! { ‣TokenStream
#[derive(Serialize, Deserialize, Debug, Default)]
struct Storage {
#(#fields),*
}
};
TokenStream::from(expanded)
}
#[derive(Debug, Serialize, Deserialize, Component)]
struct Health {
health: f32,
}
#[derive(Debug, Serialize, Deserialize, Component)]
pub struct Age {
pub age: u64,
}
storage!();

How to rename `start` and `end` range values with serde?

I have JSON objects with the following format:
{
"name": "foo",
"value": 1234,
"upper_bound": 5000,
"lower_bound": 1000
}
I'd like to use serde to work with these objects, with a struct like
struct MyObject {
name: String,
value: i32,
bound: Range<i32>,
}
Without any modifications, serializing one of these structs yields
{
"name": "foo",
"value": 1234,
"bound": {
"start": 1000,
"end": 5000
}
}
I can apply #[serde(flatten)] to get closer, yielding
{
"name": "foo",
"value": 1234,
"start": 1000,
"end": 5000
}
But adding #[serde(rename...)] doesn't seem to change anything, no matter what kind of arguments I try giving to the rename. Is it possible to flatten the range, and rename the args?
You can use serde attribute with and just use a intermediate structure letting the real implementation to serde:
use core::ops::Range;
use serde::{Deserialize, Serialize};
use serde_json::Error;
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
struct Foo {
name: String,
value: i32,
#[serde(with = "range_aux", flatten)]
bound: Range<i32>,
}
mod range_aux {
use core::ops::Range;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Serialize, Deserialize)]
struct RangeAux {
upper_bound: i32,
lower_bound: i32,
}
pub fn serialize<S>(range: &Range<i32>, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
RangeAux::serialize(
&RangeAux {
upper_bound: range.end,
lower_bound: range.start,
},
ser,
)
}
pub fn deserialize<'de, D>(d: D) -> Result<Range<i32>, D::Error>
where
D: Deserializer<'de>,
{
let range_aux: RangeAux = RangeAux::deserialize(d)?;
Ok(Range {
start: range_aux.lower_bound,
end: range_aux.upper_bound,
})
}
}
fn main() -> Result<(), Error> {
let data = r#"{"name":"foo","value":1234,"upper_bound":5000,"lower_bound":1000}"#;
let foo: Foo = serde_json::from_str(data)?;
assert_eq!(
foo,
Foo {
name: "foo".to_string(),
value: 1234,
bound: 1000..5000
}
);
let output = serde_json::to_string(&foo)?;
assert_eq!(data, output);
Ok(())
}
That very close to remote pattern but this doesn't work with generic see serde#1844.
A possible generic version:
use core::ops::Range;
use serde::{Deserialize, Serialize};
use serde_json::Error;
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
struct Foo {
name: String,
value: i32,
#[serde(with = "range_aux", flatten)]
bound: Range<i32>,
}
mod range_aux {
use core::ops::Range;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S, Idx: Serialize>(range: &Range<Idx>, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// could require Idx to be Copy or Clone instead of borrowing Idx
#[derive(Serialize)]
struct RangeAux<'a, Idx> {
upper_bound: &'a Idx,
lower_bound: &'a Idx,
}
RangeAux::serialize(
&RangeAux {
upper_bound: &range.end,
lower_bound: &range.start,
},
ser,
)
}
pub fn deserialize<'de, D, Idx: Deserialize<'de>>(d: D) -> Result<Range<Idx>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct RangeAux<Idx> {
upper_bound: Idx,
lower_bound: Idx,
}
let range_aux: RangeAux<Idx> = RangeAux::deserialize(d)?;
Ok(Range {
start: range_aux.lower_bound,
end: range_aux.upper_bound,
})
}
}
fn main() -> Result<(), Error> {
let data = r#"{"name":"foo","value":1234,"upper_bound":5000,"lower_bound":1000}"#;
let foo: Foo = serde_json::from_str(data)?;
assert_eq!(
foo,
Foo {
name: "foo".to_string(),
value: 1234,
bound: 1000..5000
}
);
let output = serde_json::to_string(&foo)?;
assert_eq!(data, output);
Ok(())
}
Not necessarily more concise than a custom serializer, but certainly a good bit more trivial is a solution with [serde(from and into)]. (I feel like I'm posting this on every serde question. :/)
You define an auxiliary, serializable struct that has the JSON structure you want:
#[derive(Deserialize, Serialize, Clone)]
struct AuxMyObject {
name: String,
value: i32,
upper_bound: i32,
lower_bound: i32,
}
Then you explain to rust how your auxiliary struct relates to the original struct. It's a bit tedious (but easy), there may be some macro crates that help lessen the typing load:
impl From<MyObject> for AuxMyObject {
fn from(from: MyObject) -> Self {
Self {
name: from.name,
value: from.value,
lower_bound: from.bound.start,
upper_bound: from.bound.end,
}
}
}
impl From<AuxMyObject> for MyObject {
fn from(from: AuxMyObject) -> Self {
Self {
name: from.name,
value: from.value,
bound: Range {
start: from.lower_bound,
end: from.upper_bound,
},
}
}
}
Lastly, you tell serde to replace your main struct with the auxiliary struct when serializing:
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(from = "AuxMyObject", into = "AuxMyObject")]
struct MyObject { … }
Playground

Can Serde deserialize JSON to one of a set of types depending on the value of a field?

I have a group of different messages that come in as JSON and can be distinguished based on a single field, but then each variant has a different collection of secondary fields:
#[derive(Debug, Serialize, Deserialize)]
struct MessageOne {
///op will always be "one"
op: String,
x: f64,
y: f64,
}
#[derive(Debug, Serialize, Deserialize)]
struct MessageTwo {
///op will always be "two"
op: String,
a: f64,
b: i64,
}
The different message types are routed to different processing functions (e.g. process_message_one, process_message_two, etc). Is there an elegant or idiomatic way to automatically select the correct message sub-type? Currently I've defined a generic message:
#[derive(Debug, Serialize, Deserialize)]
struct MessageGeneric {
op: String,
}
then parse the incoming JSON into the MessageGeneric, read the op field and then deserialize again, matching on op to select the correct message type. Full example:
#![allow(unused)]
extern crate serde; // 1.0.78
extern crate serde_json; // 1.0.27
#[macro_use]
extern crate serde_derive;
use std::collections::HashMap;
#[derive(Debug, Serialize, Deserialize)]
struct MessageGeneric {
op: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct MessageOne {
///op will always be "one"
op: String,
x: f64,
y: f64,
}
#[derive(Debug, Serialize, Deserialize)]
struct MessageTwo {
///op will always be "two"
op: String,
a: f64,
b: f64,
}
fn process_message_one(m: &MessageOne) {
println!("Processing a MessageOne: {:?}", m);
}
fn process_message_two(m: &MessageTwo) {
println!("Processing a MessageTwo: {:?}", m);
}
fn main() {
let data = r#"{
"op": "one",
"x": 1.0,
"y": 2.0
}"#;
let z: MessageGeneric = serde_json::from_str(data).unwrap();
match z.op.as_ref() {
"one" => {
let zp: MessageOne = serde_json::from_str(data).unwrap();
process_message_one(&zp);
},
"two" => {
let zp: MessageTwo = serde_json::from_str(data).unwrap();
process_message_two(&zp);
},
_ => println!("Unknown Message Type")
}
}
I've seen Serde's enum representations but it was unclear to me if/how that would be applied in this case. The messages coming in are defined by an external API, so I can't control their content beyond knowing what the variants are.
There is no point to keep "one" or "two" in your structure MessageOne and MessageTwo: if you have constructed this structure you already know if it is message one or message two.
extern crate serde; // 1.0.78
extern crate serde_json; // 1.0.27
#[macro_use]
extern crate serde_derive;
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "op")]
enum Message {
#[serde(rename = "one")]
One { x: f64, y: f64 },
#[serde(rename = "two")]
Two { a: f64, b: f64 },
}
fn process_message(message: &Message) {
println!("Processing a : {:?}", message);
}
use serde_json::Error;
fn main() -> Result<(), Error> {
let data = r#"{
"op": "one",
"x": 1.0,
"y": 2.0
}"#;
let message: Message = serde_json::from_str(data)?;
process_message(&message);
let data = r#"{
"op": "two",
"a": 1.0,
"b": 2.0
}"#;
let message: Message = serde_json::from_str(data)?;
process_message(&message);
let data = r#"{
"op": "42",
"i": 1.0,
"j": 2.0
}"#;
let message: Message = serde_json::from_str(data)?;
process_message(&message);
Ok(())
}
Standard Output
Processing a : One { x: 1.0, y: 2.0 }
Processing a : Two { a: 1.0, b: 2.0 }
Standard Error
Error: Error("unknown variant `42`, expected `one` or `two`", line: 2, column: 18)

Deserializing TOML into vector of enum with values

I'm trying to read a TOML file to create a struct that contains a vector of enums with associated values. Here's the sample code:
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate toml;
use std::fs::File;
use std::io::Read;
#[derive(Debug, Deserialize, PartialEq)]
struct Actor {
name: String,
actions: Vec<Actions>,
}
#[derive(Debug, Deserialize, PartialEq)]
enum Actions {
Wait(usize),
Move { x: usize, y: usize },
}
fn main() {
let input_file = "./sample_actor.toml";
let mut file = File::open(input_file).unwrap();
let mut file_content = String::new();
let _bytes_read = file.read_to_string(&mut file_content).unwrap();
let actor: Actor = toml::from_str(&file_content).unwrap();
println!("Read actor {:?}", actor);
}
Cargo.toml
[dependencies]
serde_derive = "1.0.10"
serde = "1.0.10"
toml = "0.4.2"
sample_actor.toml
name = "actor1"
actions = [move 3 4, wait 20, move 5 6]
I know the file looks "wrong", but I have no idea how I should write the actions in the TOML file such that the crate would be able to recognize them as an enum with X number of values.
The error I get when running this example with cargo run is the following:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error { inner: ErrorInner { kind: NumberInvalid, line: Some(1), col: 11, message: "", key: [] } }', /checkout/src/libcore/result.rs:906:4
note: Run with `RUST_BACKTRACE=1` for a backtrace.
I know that I probably need to implement FromStr for my enum to convert a string into my enum, and I briefly know that custom deserializers can be implemented to deserialize in a specific way, but I'm not sure how these pieces fit together.
It seems an equivalent example using serde_json instead of TOML works straight out (using JSON files instead of TOML of course).
JSON version of the code:
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
use serde_json::Error;
use std::fs::File;
use std::io::Read;
#[derive(Debug, Serialize, Deserialize)]
enum Foo {
bar(u32),
baz { x: u32, y: u32 },
}
#[derive(Debug, Serialize, Deserialize)]
struct Address {
street: String,
city: String,
nums: Vec<Foo>,
}
fn main() {
/*
let address = Address {
street: "10 Downing Street".to_owned(),
city: "London".to_owned(),
nums : vec![Foo::bar(1), Foo::baz{x : 2, y : 3}],
};
// Serialize it to a JSON string.
let j = serde_json::to_string(&address).unwrap();
// Print, write to a file, or send to an HTTP server.
println!("{}", j);
*/
let input_file = "./sample_address.json";
let mut file = File::open(input_file).unwrap();
let mut file_content = String::new();
let _bytes_read = file.read_to_string(&mut file_content).unwrap();
let address: Address = serde_json::from_str(&file_content).unwrap();
println!("{:?}", address);
}
The JSON data read/written in this example is:
Address { street: "10 Downing Street", city: "London", nums: [bar(1), baz { x: 2, y: 3 }] }
Maybe the TOML crate can't support my use-case?
Serde has lots of options for serializing enums. One that works for your case:
use serde::{Deserialize, Serialize}; // 1.0.117
use toml; // 0.5.7
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", content = "args")]
enum Actions {
Wait(usize),
Move { x: usize, y: usize },
}
fn main() {
let a_wait = Actions::Wait(5);
println!("{}", toml::to_string(&a_wait).unwrap());
let a_move = Actions::Move { x: 1, y: 1 };
println!("{}", toml::to_string(&a_move).unwrap());
}
type = "Wait"
args = 5
type = "Move"
[args]
x = 1
y = 1

Resources