Deserializing an unknown enum - json

Why does this code not deserialize the messages correctly?
The first message received is expected to fail, because the data field = {}, but after that the data field matches the Trade structure which is part of the flattened enum Data.
What am I doing wrong?
Here is the output:
***> {"event":"bts:subscription_succeeded","channel":"live_trades_btcusd","data":{}}
===> Error("no variant of enum Data found in flattened data", line: 1, column: 79)
***> {"data":{"id":220315257,"timestamp":"1644180636","amount":0.03208,"amount_str":"0.03208000","price":41586.11,"price_str":"41586.11","type":0,"microtimestamp":"1644180636419000","buy_order_id":1455495830577152,"sell_order_id":1455495778422789},"channel":"live_trades_btcusd","event":"trade"}
===> Error("no variant of enum Data found in flattened data", line: 1, column: 290)
***> {"data":{"id":220315276,"timestamp":"1644180648","amount":0.02037389,"amount_str":"0.02037389","price":41586.11,"price_str":"41586.11","type":1,"microtimestamp":"1644180648235000","buy_order_id":1455495864238080,"sell_order_id":1455495878971395},"channel":"live_trades_btcusd","event":"trade"}
use serde::{Deserialize, Serialize};
use serde_json::json;
use tungstenite::{connect, Message};
use url::Url;
#[derive(Serialize, Deserialize, Debug)]
enum Data {
None,
Trade(Trade),
}
#[derive(Serialize, Deserialize, Debug)]
struct Msg {
channel: String,
event: String,
#[serde(flatten)]
data: Data,
}
#[derive(Serialize, Deserialize, Debug)]
struct Trade {
id: u32,
amount: f32,
amount_str: String,
buy_order_id: u64,
microtimestamp: String,
price: f32,
price_str: String,
sell_order_id: u64,
timestamp: String,
#[serde(rename = "type")]
_type: u8,
}
let result: Result<Msg, serde_json::Error> = serde_json::from_str(msg.to_text().unwrap());
let _value = match result {
Ok(msg) => {
println!("---> {:?}", msg);
}
Err(err) => {
println!("===> {:?}", err);
continue;
}
};
https://pastebin.com/JZqPkmWC

I solved it by both removing the #[serde(flatten)] and also adding #[serde(untagged)]. Thanks for the help!
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
enum Data {
Trade(Trade),
Order(Order),
None {},
}
#[derive(Serialize, Deserialize, Debug)]
struct Msg {
channel: String,
event: String,
data: Data,
}

Related

Inserting Json from an api call to a struct but a there is a camp that sometimes is a vec and sometimes is null

i call an api that returns a json like this one
[
{
"id":"b8b2f91952f1fb757663073fa8f6be73",
"name":"test1",
"colors":[
{
"name":"Red",
"number":"0"
},
{
"name":"Blue",
"number":"1"
}
]
},
{
"id":"91ff6b1fb7b8b2f57663073952fa8e73",
"name":"test2",
"colors": null
}
]
i made this struct
#[derive(Serialize, Deserialize, Debug)]
struct FinalScores {
id: String,
name: String,
colors: Vec<Colors>
}
#[derive(Serialize, Deserialize, Debug)]
struct Colors{
name: String,
number: String
}
i map the json into the struct with this
let score_request_url = "https://api.test.com";
let score_response = reqwest::get(score_request_url).await?;
let score_infos: Vec<FinalScores> = score_response.json().await?;
but i get an error because he is expecting always a vec of Colors in the colors camp but sometimes is not a vec but is just null
invalid type: null, expected a string
anyone who know how to solve this?
I'd recommend you to use serde_with's DefaultOnNull:
#[serde_with::serde_as]
#[derive(Serialize, Deserialize, Debug)]
struct FinalScores {
id: String,
name: String,
#[serde_as(deserialize_as = "serde_with::DefaultOnNull")]
colors: Vec<Colors>
}

Rust - #[serde(tag="a.x")] attribute on a sub field in JSON

I have these two structs:
#[derive(Debug, Deserialize, Serialize)]
struct RequestBase {
id: i32,
method_name: String,
}
#[derive(Debug, Deserialize, Serialize)]
struct LoginRequest {
base: RequestBase,
// Login
email: String,
password: String,
}
#[derive(Debug, Deserialize, Serialize)]
struct CreateAccountRequest{
base: RequestBase,
// Account
email: String,
password: String,
}
I want to map a json from ist "base.method_name" property.
I tried to use #[serde(tag="base.method_name")] but it can't access a sub property which is not in first scope.
An example of JSON:
// This should map to LoginRequest struct.
{"base":{"id":1,"method_name":"LoginRequest"},"email":"a#mail.com","password":"xxxx"}
// This should map to CreateAccountRequest struct.
{"base":{"id":1,"method_name":"CreateAccountRequest"},"email":"a#mail.com","password":"xxxx"}
How can I do this? How can I separate LoginRequest and CreateAccountRequest (which has same fields) structs from base.method_name?
Thanks!

deserializing serde_json from API in rust

I am attempting to scrape the JSON from this endpoint https://prices.runescape.wiki/api/v1/osrs/latest .
#[derive(Serialize, Deserialize, Debug)]
struct Latest {
high: u32,
highTime: String,
low: String,
lowTime: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct Data {
#[serde(with = "serde_with::json::nested")]
data: Vec<Latest>,
}
#[derive(Serialize, Deserialize, Debug)]
struct Datav2 {
#[serde(with = "serde_with::json::nested")]
data: HashMap<u32, Vec<Latest>>,
}
#[cfg(not(target_arch = "wasm32"))]
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let res = reqwest::get(url).await?;
let response = &res.json::<Datav2>().await?;
}
I've tried two versions of the datastructure. Data is using a vector of latest, but i noticed each object has a unique ID, so in DataV2 i tried using a hashmap but i get the same error. I've also tried unnested versions without using Serde_with.
I get the error Error: reqwest::Error { kind: Decode, source: Error("invalid type: map, expected valid json object", line: 1, column: 8)
It seems my datastructure is messed up, but have been trying for hours to figure out the correct data structure to use.
There are multiple issues with your current code.
Datav2 is closer, but still not correct. It is not a HashMap<u32, Vec<Latest>>but a HashMap<u32, Latest>. There is no need to have another Vec, since each number is assigned a value in the JSON.
highTime, low, lowTime are not of type String (since they have no quotes in the JSON), but are unsigned numbers (to be on the safe side I just assumed them to be u64).
Apparently the fields of Latest can be null, so they need to be Options.
I would still use snake_case for the field names in the structs and then rename them to camelCase using the serde macro
I modified your code like I would do this, in order to give you an example of how it could be done:
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Latest {
high: Option<u64>,
high_time: Option<u64>,
low: Option<u64>,
low_time: Option<u64>,
}
#[derive(Serialize, Deserialize, Debug)]
struct Data {
data: HashMap<u64, Latest>,
}
#[cfg(not(target_arch = "wasm32"))]
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let url = "https://prices.runescape.wiki/api/v1/osrs/latest";
let res = reqwest::get(url).await?;
let response = &res.json::<Data>().await?;
println!("{:#?}", response);
Ok(())
}

Serialize/Deserialize CSV with nested enum/struct with serde in Rust

I want to serialize/deserialize a CSV file with variable row length and content, like the following:
./test.csv
Message,20200202T102030,Some message content
Measurement,20200202T102031,10,30,40,2
AnotherMeasurement,20200202T102034,0,2
In my opinion, the easiest way to represent this is the following enum:
#[derive(Debug, Serialize, Deserialize)]
pub enum Record {
Message { timestamp: String, content: String }, // timestamp is String because of simplicity
Measurement { timestamp: String, a: u32, b: u32, c: u32, d: u32 },
AnotherMeasurement { timestamp: String, a: u32, b: u32 },
}
Cargo.toml
[dependencies]¬
csv = "^1.1.6"¬
serde = { version = "^1", features = ["derive"] }
Running the following
main.rs
fn example() -> Result<(), Box<dyn Error>> {
let mut rdr = csv::ReaderBuilder::new()
.has_headers(false)
.delimiter(b',')
.flexible(true)
.double_quote(false)
.from_path("./test.csv")
.unwrap();
for result in rdr.deserialize() {
let record: Record = result?;
println!("{:?}", record);
}
Ok(())
}
fn write_msg() -> Result<(), Box<dyn Error>> {
let msg = Record::Message {
timestamp: String::from("time"),
content: String::from("content"),
};
let mut wtr = csv::WriterBuilder::new()
.has_headers(false)
.flexible(true)
.double_quote(false)
.from_writer(std::io::stdout());
wtr.serialize(msg)?;
wtr.flush()?;
Ok(())
}
fn main() {
if let Err(err) = example() {
println!("error running example: {}", err);
}
if let Err(err) = write_msg() {
println!("error running example: {}", err);
}
}
prints
error running example: CSV deserialize error: record 0 (line: 1, byte: 0): invalid type: unit variant, expected struct variant
error running example: CSV write error: serializing enum struct variants is not supported
Is there an easy solution to do this with serde and csv? I feel like I missed one or two serde attributes, but I was not able to find the right one in the documentation yet.
EDITS
Netwave suggested adding the #[serde(tag = "type")] attribute. Serializing now works, Deserializing gives the following error:
error running example: CSV deserialize error: record 0 (line: 1, byte: 0): invalid type: string "Message", expected internally tagged enum Record
Research I did that did not lead to a solution yet
Is there a way to "flatten" enums for (de)serialization in Rust?
https://docs.rs/csv/1.1.6/csv/tutorial/index.html
Custom serde serialization for enum type
https://serde.rs/enum-representations.html
Make your enum tagged (internally tagged specifically):
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Record {
Message { timestamp: String, content: String }, // timestamp is String because of simplicity
Measurement { timestamp: String, a: u32, b: u32, c: u32, d: u32 },
AnotherMeasurement { timestamp: String, a: u32, b: u32 },
}
Playground

How do I serialize an enum to a number and deserialize from a number via serde-json?

struct ResponseData<T> {
success : bool,
res_data : T,
}
struct FooRes {
result:RESULT,
}
num RESULT {
RESULT_OK = 0,
RESULT_NG = 1,
}
fn test(){
let s = ResponseData::<FooRes>{
success : true,
res_data : FooRes{
result:RESULT::RESULT_OK,
},
};
let st = serde_json::to_string(&s).unwrap();
println!("json={}",st);
json={"success":true,"resData":{"result":"RESULT_OK"}}
I need the outcome to be {"result":0}, not {"result":"RESULT_OK"}, when serializing the enum into a number value,
and {"success":true,"resData":{"result":0}} to deserialize to the enum member result.
struct FooRes {
result:RESULT,
}
How do I do this?
Thunks, I solved
https://serde.rs/enum-number.html
Correct
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug)]
Not
#[derive(Debug, Serialize, Deserialize)]
On Enum , tnx