Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use of Serde's #[serde(transparent)]

Tags:

json

rust

serde

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 }] }
like image 285
Jack Chidley Avatar asked Dec 31 '25 06:12

Jack Chidley


1 Answers

#[serde(transparent)] tells serde to serialize/deserialize the struct as if the struct was its single field.

This is useful when using the NewType pattern, especially when you would prefer a name-fielded struct over a tuple-struct for clarity or to avoid ambiguity (e.g., the inner type doesn't convey enough meaning without a label).

#[cfg(test)]
mod tests {
    #[test]
    fn demonstrate_serde_transparent() -> Result<(), serde_json::Error> {
        use ::serde::{ Deserialize, Serialize };

        #[derive(Deserialize, Serialize)]
        #[serde(transparent)]
        struct List<T> {
            elements: Vec<T>, // unambiguous for clarity.
        }

        let ty = List { elements: vec![42] };
        let serialized = serde_json::to_string(&ty).unwrap();

        assert_eq!(serialized, r#"[42]"#);

        #[derive(Deserialize, Serialize)]
        struct ListObject<T> {
            elements: Vec<T>,
        }

        let ty = ListObject { elements: vec![42] };
        let serialized = serde_json::to_string(&ty).unwrap();

        assert_eq!(serialized, r#"{"elements":[42]}"#);

        Ok(())
    }
}
like image 96
dubble Avatar answered Jan 02 '26 18:01

dubble



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!