How can I deserialize an XML-string (using serde derive) returned by an API that might have different children?
If I call an API it might return one of the following result sets:
use serde::Deserialize;
use serde_xml_rs::from_str;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct Error {
code: u32,
message: String,
}
#[derive(Debug, Deserialize)]
struct Item {
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct DataSet {
error: Option<Error>,
data: Option<Vec<Item>>
}
fn main() {
let error = r#"
<DataSet>
<Error>
<Code>0</Code>
<Message>error</Message>
</Error>
</DataSet>
"#;
let dataset: DataSet = from_str(error).unwrap();
println!("{:#?}", dataset);
let items = r#"
<DataSet>
<Item>..</Item>
<Item>..</Item>
</DataSet>
"#;
let dataset: DataSet = from_str(items).unwrap();
println!("{:#?}", dataset);
}
[package]
name = "dataset"
version = "0.1.0"
edition = "2018"
[dependencies]
serde = { version = "1", features = ["derive"] }
serde-xml-rs = "0.3"
But I feel like there should be a more idiomatic way of achieving this. Especially since it would make matching off the data a lot easier.
I've tried using #[serde(flatten)], and I've tried setting ApiResponse to a tuple-struct and a struct with a single tuple parameter. No luck.
Is this possible without having to build a custom deserializer?
Take a look at some of the serde examples which might be able to help you here. Specifically the examples/docs on untagged enums which allow you to define many ways to deserialize into a single enum type.
Related
I have this code:
let vid = VideoLayer::VideoConcatLayer(VideoConcatLayer {
list: vec![VideoLayer::VideoAssetLayer(VideoAssetLayer {
asset: T3Val::Ready(
Ready {
val: "hello".to_string()
})
})]
});
Basically VideoLayer and T3Val are enums and VideoConcatLayer, VideoAssetLayer, and Ready are structs. The problem is that when I try to serialize it with serde, the "type" field is duplicated and it throws an error.
The serialized result is here:
{"type":"VideoConcatLayer","type":"VideoConcatLayer","list":[{"type":"VideoAssetLayer","type":"VideoAssetLayer","asset":{"type":"Ready","type":"Ready","val":"hello"}}]}
Since you won't share the code to reproduce the problem, I had to guess:
use serde::Serialize;
#[derive(Serialize)]
#[serde(tag = "type")]
enum VideoLayer {
VideoConcatLayer(VideoConcatLayer),
}
#[derive(Serialize)]
#[serde(tag = "type")]
struct VideoConcatLayer {
list: Vec<…>,
}
Delete the #[serde(tag = "type")] from the structs, only put it onto enums, and your problem will go away.
And for future questions, please always share the full code necessary to reproduce your problem, maybe with a playground link or including a list of dependencies.
My goal is to (de)serialize objects with RFC-3339 timestamps from Json to Rust structs (and vice versa) using serde and time-rs.
I would expect this ...
use serde::Deserialize;
use time::{OffsetDateTime};
#[derive(Deserialize)]
pub struct DtoTest {
pub timestamp: OffsetDateTime,
}
fn main() {
let deserialization_result = serde_json::from_str::<DtoTest>("{\"timestamp\": \"2022-07-08T09:10:11Z\"}");
let dto = deserialization_result.expect("This should not panic");
println!("{}", dto.timestamp);
}
... to create the struct and display the timestamp as the output, but I get ...
thread 'main' panicked at 'This should not panic: Error("invalid type: string \"2022-07-08T09:10:11Z\", expected an `OffsetDateTime`", line: 1, column: 36)', src/main.rs:12:38
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
My dependencies look like this:
[dependencies]
serde = { version = "1.0.138", features = ["derive"] }
serde_json = "1.0.82"
time = { version = "0.3.11", features = ["serde"] }
According to the documentation of the time-rs crate, this seems to be possible but I must be missing something.
The default serialization format for time is some internal format. If you want other formats, you should enable the serde-well-known feature and use the serde module to choose the format you want:
#[derive(Deserialize)]
pub struct DtoTest {
#[serde(with = "time::serde::rfc3339")]
pub timestamp: OffsetDateTime,
}
The solution below is based on the serde_with crate. As per its documentation, it aims to be more flexible and composable.
use serde_with::serde_as;
use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;
#[serde_as]
#[derive(Deserialize)]
pub struct DtoTest {
#[serde_as(as = "Rfc3339")]
pub timestamp: OffsetDateTime,
}
And the Cargo.toml file should have:
[dependencies]
serde_with = { version = "2", features = ["time_0_3"] }
At the following page are listed all De/Serialize transformations available.
I am new to Rust. I am trying to parse yaml in Rust using serde_yaml, but can't make the code compile:
My Cargo.toml:
[package]
name = "apmdeps"
version = "0.1.0"
authors = ["Roger Rabbit"]
edition = "2018"
[dependencies]
git2 = "0.10"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8"
I tried to adapt the code sample found on the serde_yaml website, to no avail:
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Dependency {
url: String,
tag: String,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Project {
dependencies: Vec<Dependency>,
}
fn main() {
let s = "---\ndependencies:\n--url:http://test1\ntag:tag1\n--url:http://test2\ntag:tag2";
let project: Project = serde_yaml::from_str(&s);
}
I get the following error:
error[E0308]: mismatched types
--> src/main.rs:17:28
|
17 | let project: Project = serde_yaml::from_str(&s);
| ^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Project`, found enum `std::result::Result`
|
= note: expected type `Project`
found type `std::result::Result<_, serde_yaml::error::Error>`
Your problem is that serde_yaml::from_str(&s) does not return a Dependency struct directly as you expect, but a Result struct.
Result structs are rust's way of error handling. Results are either Ok(value) or an Err and you need to check which one it is. Typically in a match expression.
In your case the parsed dependency is wrapped in Ok(project) if parsing the string is successful.
I could get your code to compile with the following match expression:
let project_result : Result<Project, _> = serde_yaml::from_str(&s);
match project_result {
Ok(project) => println!("dependencies = {:?}", project),
Err(_) => println!("Error!")
}
The next problem however is that your string seemed not proper yaml, at least not as expected from serde, and I do get "Error!" from the program.
I changed your string to the following to get some useful output. I don't know if that is your intended yaml though.
let s = "---\ndependencies:\n - {url: http://test1, tag: tag1}\n - {url: http://test2, tag: tag2}\n";
Serde documentation says:
All of these can be serialized using Serde out of the box.
serde_json is just for the example, not required in general.
This is exactly what I need, a basic serialization of a struct into some basic binary format. I don't need JSON format, I want to keep it simple to be able to store struct in a file or send it to the network. The documentation is not clear on how to use serde for basic (binary or default) serialization, it only shows example with a JSON but this is not what I am looking for. I also don't want to implement my own serialize method, I want to use the default methods that Serde provides.
This is my example, so how do I make it work?
use serde::{Serialize, Deserialize,Serializer};
#[derive(Serialize, Deserialize, Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let point = Point { x: 1, y: 2 };
//let serialized = serde::serialize(&point).unwrap(); // <-- doesnt work!
//let serialized = Serializer::serialize(&point); // <-- doesnt work!
//let serialized = point.serialize(Serializer); // <-- doesn't work!
println!("data = {:?}", serialized);
}
Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b57a77399280f19664bb004201319b32
This is my dependency line:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
As noted in the comments, there's no "default" format provided.
You have to pick the binary format you want and include it as an additional dependency, just as you would with serde_json.
There's a list of formats at https://serde.rs/#data-formats. Of those serde_json is the only package hosted under https://github.com/serde-rs, all the binary formats are "third-party". Cross-referencing it with the list of crates tagged "serde" sorted by recent downloads, the CBOR crate seems popular.
I am trying to use Serde to deserialize JSON (serde-json) and XML (serde-xml-rs) files based on the following struct:
use serde_derive::Deserialize;
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct SchemaConfig {
pub name: String,
#[serde(rename = "Cube")]
pub cubes: Vec<CubeConfig>,
}
The fields I am deserializing on have different names based on the file type. In this case, I would like for a JSON file to have a cubes key with a list of cubes, but the equivalent in XML would be multiple <Cube /> elements.
I can't figure out how to accept both cubes and Cube as keys for the deserialization. The closest thing I found was the #[serde(rename = "Cube")] option but when I use that the JSON deserialization stops working since it only accepts the Cube key. If I remove that option, the XML deserialization stops working as it then only accepts cubes as the key.
Is there a simple way to accomplish this in Serde?
I encourage you to read the Serde documentation. The field attributes chapter introduces the alias attribute, emphasis mine:
#[serde(alias = "name")]
Deserialize this field from the given name or from its Rust name. May
be repeated to specify multiple possible names for the same field.
use serde::Deserialize; // 1.0.88
use serde_json; // 1.0.38
#[derive(Debug, Deserialize)]
struct SchemaConfig {
#[serde(alias = "fancy_square", alias = "KUBE")]
cube: [i32; 3],
}
fn main() -> Result<(), Box<std::error::Error>> {
let input1 = r#"{
"fancy_square": [1, 2, 3]
}"#;
let input2 = r#"{
"KUBE": [4, 5, 6]
}"#;
let one: SchemaConfig = serde_json::from_str(input1)?;
let two: SchemaConfig = serde_json::from_str(input2)?;
println!("{:?}", one);
println!("{:?}", two);
Ok(())
}
I would like for a JSON file to have a cubes key with a list of cubes, but the equivalent in XML would be multiple <Cube /> elements.
This certainly sounds like you want two different structures to your files. In that case, look at something like:
How to transform fields during deserialization using Serde?
How do I serialize an enum without including the name of the enum variant?