Reputation: 136
I needed to deserialise a "bare" json array of arrays so I wrote the code below. It works as intended and is based on these stackoverflow questions Deserializing a DateTime from a string millisecond timestamp with serde and Rust: Deserialize a JSON array into a very simple custom table. However, I don't know why #[serde(transparent)]
is required and what this attribute is doing.
I read the official documentation but I didn't understand it. Can someone explain it in simpler terms?
from Serde's Container attributes
#[serde(transparent)] Serialize and deserialize a newtype struct or a braced struct with one field exactly the same as if its one field were serialized and deserialized by itself. Analogous to #[repr(transparent)].
from The Rustonomicon Alternative representations
#[repr(transparent)] can only be used on a struct or single-variant enum that has a single non-zero-sized field (there may be additional zero-sized fields). The effect is that the layout and ABI of the whole struct/enum is guaranteed to be the same as that one field.#[repr(transparent)] can only be used on a struct or single-variant enum that has a single non-zero-sized field (there may be additional zero-sized fields). The effect is that the layout and ABI of the whole struct/enum is guaranteed to be the same as that one field.
from lurklurk's Effective Rust
If size efficiency or binary compatibility is a concern, then the #[repr(transparent)] attribute ensures that a newtype has the same representation in memory as the inner type.
use chrono::{DateTime, Utc};
use serde::Deserialize;
use serde_json::{self, Result};
use serde_with::formats::Flexible;
use serde_with::TimestampSeconds;
const EMONCMS_FEED_DATA: &str = r#"
[
[
1716705669,
272430
],
[
1716705729,
272436
]
]"#;
#[serde_with::serde_as]
#[derive(Deserialize, Debug)]
pub struct Msg {
#[serde_as(as = "TimestampSeconds<String, Flexible>")]
pub date_time: DateTime<Utc>,
pub msg: i32,
}
#[derive(Deserialize, Debug)]
#[serde(transparent)]
pub struct EmoncmsMsg {
pub rows: Vec<Msg>,
}
impl EmoncmsMsg {
pub fn new(data: &str) -> Result<EmoncmsMsg> {
serde_json::from_str(data)
}
}
fn main() {
let table: EmoncmsMsg = EmoncmsMsg::new(EMONCMS_FEED_DATA).unwrap();
assert_eq!(table.rows.len(), 2);
println!("{:?}", table);
}
Output:
EmoncmsMsg { rows: [Msg { date_time: 2024-05-26T06:41:09Z, msg: 272430 }, Msg { date_time: 2024-05-26T06:42:09Z, msg: 272436 }] }
Upvotes: 0
Views: 1255
Reputation: 136
This is a work in progress answer to my own question, feel free to comment or provide a better answer.
Some helpful details of how serde works Decrusting the serde crate
The short answer is that add #[derive(Deserialize)]
to a struct means that serde will build it's own implementation deserialization and that implementation includes matching field names in the JSON data with the struct field names. The order of the fields cannot be relied upon, hence the need to match on field names. Obviously this will fail. #[serde(transparent)]
stops that match happening.
Another method for doing this appears to be using tuples. This gives very different outputs.
use chrono::{DateTime, Utc};
use serde::{Deserialize};
use serde_json::{self, Result};
use serde_with::formats::Flexible;
use serde_with::TimestampSeconds;
const EMONCMS_FEED_DATA: &str = r#"
[
[
1716705669,
272430
],
[
1716705729,
272436
]
]"#;
#[serde_with::serde_as]
#[derive(Deserialize, Debug)]
struct MsgTuple(
#[serde_as(as = "TimestampSeconds<String, Flexible>")] DateTime<Utc>,
i32,
);
#[derive(Deserialize, Debug)]
struct EmoncmsMsgTuple(Vec<MsgTuple>);
impl EmoncmsMsgTuple {
pub fn new(data: &str) -> Result<EmoncmsMsgTuple> {
serde_json::from_str(data)
}
}
fn main() {
let table: EmoncmsMsgTuple = EmoncmsMsgTuple::new(EMONCMS_FEED_DATA).unwrap();
println!("{:?}", table);
}
output:
EmoncmsMsgTuple([MsgTuple(2024-05-26T06:41:09Z, 272430), MsgTuple(2024-05-26T06:42:09Z, 272436)])
Upvotes: 0