diff --git a/src/serializer.rs b/src/serializer.rs index 6d47c46e..f7eea73a 100644 --- a/src/serializer.rs +++ b/src/serializer.rs @@ -1056,7 +1056,10 @@ use std::collections::HashMap; use crate::{ColNum, Format, RowNum, Worksheet, XlsxError}; -use serde::{ser, Serialize}; +use serde::{ + de::{self, Visitor}, + ser, Deserialize, Deserializer, Serialize, +}; // ----------------------------------------------------------------------- // SerializerState, a struct to maintain row/column state and other metadata @@ -2400,3 +2403,64 @@ impl<'a> ser::SerializeStructVariant for &'a mut SerializerHeader { Ok(()) } } + +// ----------------------------------------------------------------------- +// Header DeSerializer. This is the a simplified implementation of the +// Deserializer trait to capture the headers/field names only. +// ----------------------------------------------------------------------- +pub(crate) struct DeSerializerHeader<'a> { + pub(crate) struct_name: &'a mut &'static str, + pub(crate) field_names: &'a mut &'static [&'static str], +} + +impl<'de, 'a> Deserializer<'de> for DeSerializerHeader<'a> { + type Error = serde::de::value::Error; + + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + _visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + *self.struct_name = name; + *self.field_names = fields; + Err(de::Error::custom("Deserialization error")) + } + + fn deserialize_any(self, _visitor: V) -> Result + where + V: Visitor<'de>, + { + Err(de::Error::custom("Deserialization error")) + } + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes + byte_buf option unit unit_struct newtype_struct seq tuple + tuple_struct map enum identifier ignored_any + } +} + +pub(crate) fn deserialize_headers<'de, T>() -> SerializerHeader +where + T: Deserialize<'de>, +{ + let mut struct_name = ""; + let mut field_names: &[&str] = &[""]; + + let _ = T::deserialize(DeSerializerHeader { + struct_name: &mut struct_name, + field_names: &mut field_names, + }); + + let struct_name = struct_name.to_string(); + let field_names = field_names.iter().map(|&s| s.to_string()).collect(); + + SerializerHeader { + struct_name, + field_names, + } +} diff --git a/src/worksheet.rs b/src/worksheet.rs index 898f27b1..3bc21d23 100644 --- a/src/worksheet.rs +++ b/src/worksheet.rs @@ -32,6 +32,12 @@ use crate::SerializerHeader; #[cfg(feature = "serde")] use crate::CustomSerializeHeader; +#[cfg(feature = "serde")] +use serde::Deserialize; + +#[cfg(feature = "serde")] +use crate::deserialize_headers; + use crate::drawing::{Drawing, DrawingCoordinates, DrawingInfo, DrawingObject}; use crate::error::XlsxError; use crate::format::Format; @@ -6537,6 +6543,51 @@ impl Worksheet { Ok(self) } + /// TODO + /// + /// # Errors + /// + #[cfg(feature = "serde")] + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] + pub fn serialize_headers_from_type<'de, T>( + &mut self, + row: RowNum, + col: ColNum, + ) -> Result<&mut Worksheet, XlsxError> + where + T: Deserialize<'de>, + { + self.serialize_headers_with_format_from_type::(row, col, &Format::default()) + } + + /// TODO + /// + /// # Errors + /// + #[cfg(feature = "serde")] + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] + pub fn serialize_headers_with_format_from_type<'de, T>( + &mut self, + row: RowNum, + col: ColNum, + format: &Format, + ) -> Result<&mut Worksheet, XlsxError> + where + T: Deserialize<'de>, + { + // Deserialize the struct to determine the type name and the fields. + let headers = deserialize_headers::(); + + // Convert the field names to custom header structs. + let custom_headers: Vec = headers + .field_names + .iter() + .map(|name| CustomSerializeHeader::new_with_format(name, format)) + .collect(); + + self.serialize_headers_with_options(row, col, headers.struct_name, &custom_headers) + } + // Serialize the parent data structure to the worksheet. #[cfg(feature = "serde")] fn serialize_data_structure(&mut self, data_structure: &T) -> Result<(), XlsxError> diff --git a/tests/integration/serde01.rs b/tests/integration/serde01.rs index 00e49799..01a666b3 100644 --- a/tests/integration/serde01.rs +++ b/tests/integration/serde01.rs @@ -7,7 +7,7 @@ use crate::common; use rust_xlsxwriter::{Workbook, XlsxError}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; // Test case for Serde serialization. First test isn't serialized. fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> { @@ -163,6 +163,28 @@ fn create_new_xlsx_file_7(filename: &str) -> Result<(), XlsxError> { Ok(()) } +// Test case for Serde serialization. Header deserialization. +fn create_new_xlsx_file_8(filename: &str) -> Result<(), XlsxError> { + let mut workbook = Workbook::new(); + let worksheet = workbook.add_worksheet(); + + // Create a serializable test struct. + #[derive(Deserialize, Serialize)] + struct MyStruct { + col1: u8, + col2: i8, + } + + let data = MyStruct { col1: 1, col2: -1 }; + + worksheet.serialize_headers_from_type::(0, 0)?; + worksheet.serialize(&data)?; + + workbook.save(filename)?; + + Ok(()) +} + #[test] fn test_serde01_1() { let test_runner = common::TestRunner::new() @@ -246,3 +268,15 @@ fn test_serde01_7() { test_runner.assert_eq(); test_runner.cleanup(); } + +#[test] +fn test_serde01_8() { + let test_runner = common::TestRunner::new() + .set_name("serde01") + .set_function(create_new_xlsx_file_8) + .unique("8") + .initialize(); + + test_runner.assert_eq(); + test_runner.cleanup(); +} diff --git a/tests/integration/serde06.rs b/tests/integration/serde06.rs index 2a46f6e8..4febfd16 100644 --- a/tests/integration/serde06.rs +++ b/tests/integration/serde06.rs @@ -7,7 +7,7 @@ use crate::common; use rust_xlsxwriter::{CustomSerializeHeader, Format, Workbook, XlsxError}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; // Test case for Serde serialization. First test isn't serialized. fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> { @@ -93,6 +93,34 @@ fn create_new_xlsx_file_3(filename: &str) -> Result<(), XlsxError> { Ok(()) } +// Test case for Serde serialization. Header deserialization. +fn create_new_xlsx_file_4(filename: &str) -> Result<(), XlsxError> { + let mut workbook = Workbook::new(); + let worksheet = workbook.add_worksheet(); + let bold = Format::new().set_bold(); + + worksheet.set_paper_size(9); + + // Create a serializable test struct. + #[derive(Deserialize, Serialize)] + struct MyStruct { + col1: Vec, + col2: Vec, + } + + let data = MyStruct { + col1: vec![123, 456, 789], + col2: vec![true, false, true], + }; + + worksheet.serialize_headers_with_format_from_type::(0, 0, &bold)?; + worksheet.serialize(&data)?; + + workbook.save(filename)?; + + Ok(()) +} + #[test] fn test_serde06_1() { let test_runner = common::TestRunner::new() @@ -128,3 +156,15 @@ fn test_serde06_3() { test_runner.assert_eq(); test_runner.cleanup(); } + +#[test] +fn test_serde06_4() { + let test_runner = common::TestRunner::new() + .set_name("serde06") + .set_function(create_new_xlsx_file_4) + .unique("4") + .initialize(); + + test_runner.assert_eq(); + test_runner.cleanup(); +}