diff --git a/README.md b/README.md index 35dcdc8..4bfc9aa 100644 --- a/README.md +++ b/README.md @@ -45,50 +45,45 @@ fn example() -> Result<(), Error> { } ``` -Note if you want to deserialize a column that may have invalid types (i.e. a float where some values may be strings), you can use Serde's `deserialize_with` field attribute: +Calamine provides helper functions to deal with invalid type values. For instance if you +want to deserialize a column which should contain floats but may also contain invalid values +(i.e. strings), you can use the [`deserialize_as_f64_or_none`] helper function with Serde's +[`deserialize_with`](https://serde.rs/field-attrs.html) field attribute: ```rust -use serde::{DataType, Deserialize}; -use calamine::{RangeDeserializerBuilder, Reader, Xlsx}; +use calamine::{deserialize_as_f64_or_none, open_workbook, RangeDeserializerBuilder, Reader, Xlsx}; +use serde::Deserialize; #[derive(Deserialize)] struct Record { metric: String, - #[serde(deserialize_with = "de_opt_f64")] + #[serde(deserialize_with = "deserialize_as_f64_or_none")] value: Option, } -// Convert value cell to Some(f64) if float or int, else None -fn de_opt_f64<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - let data = calamine::Data::deserialize(deserializer)?; - if let Some(float) = data.as_f64() { - Ok(Some(float)) - } else { - Ok(None) - } -} - -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { let path = format!("{}/tests/excel.xlsx", env!("CARGO_MANIFEST_DIR")); let mut excel: Xlsx<_> = open_workbook(path)?; let range = excel - .worksheet_range("Sheet1") - .ok_or(calamine::Error::Msg("Cannot find Sheet1"))??; + .worksheet_range("Sheet1") + .map_err(|_| calamine::Error::Msg("Cannot find Sheet1"))?; - let iter_result = + let iter_records = RangeDeserializerBuilder::with_headers(&["metric", "value"]).from_range(&range)?; - for result in iter_results { + for result in iter_records { let record: Record = result?; println!("metric={:?}, value={:?}", record.metric, record.value); } + + Ok(()) } ``` +The [`deserialize_as_f64_or_none`] function will discard all invalid values, if you want to +return them as `String` you can use the [`deserialize_as_f64_or_string`] function instead. + ### Reader: Simple ```rust diff --git a/src/lib.rs b/src/lib.rs index d757e5a..adfe2f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,7 @@ mod de; mod errors; pub mod vba; -use serde::de::DeserializeOwned; +use serde::de::{Deserialize, DeserializeOwned, Deserializer}; use std::borrow::Cow; use std::cmp::{max, min}; use std::fmt; @@ -890,3 +890,199 @@ impl Table { &self.data } } + +/// A helper function to deserialize cell values as `i64`, +/// useful when cells may also contain invalid values (i.e. strings). +/// It applies the [`as_i64`] method to the cell value, and returns +/// `Ok(Some(value_as_i64))` if successful or `Ok(None)` if unsuccessful, +/// therefore never failing. This function is intended to be used with Serde's +/// [`deserialize_with`](https://serde.rs/field-attrs.html) field attribute. +pub fn deserialize_as_i64_or_none<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let data = Data::deserialize(deserializer)?; + Ok(data.as_i64()) +} + +/// A helper function to deserialize cell values as `i64`, +/// useful when cells may also contain invalid values (i.e. strings). +/// It applies the [`as_i64`] method to the cell value, and returns +/// `Ok(Ok(value_as_i64))` if successful or `Ok(Err(value_to_string))` if unsuccessful, +/// therefore never failing. This function is intended to be used with Serde's +/// [`deserialize_with`](https://serde.rs/field-attrs.html) field attribute. +pub fn deserialize_as_i64_or_string<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let data = Data::deserialize(deserializer)?; + Ok(data.as_i64().ok_or_else(|| data.to_string())) +} + +/// A helper function to deserialize cell values as `f64`, +/// useful when cells may also contain invalid values (i.e. strings). +/// It applies the [`as_f64`] method to the cell value, and returns +/// `Ok(Some(value_as_f64))` if successful or `Ok(None)` if unsuccessful, +/// therefore never failing. This function is intended to be used with Serde's +/// [`deserialize_with`](https://serde.rs/field-attrs.html) field attribute. +pub fn deserialize_as_f64_or_none<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let data = Data::deserialize(deserializer)?; + Ok(data.as_f64()) +} + +/// A helper function to deserialize cell values as `f64`, +/// useful when cells may also contain invalid values (i.e. strings). +/// It applies the [`as_f64`] method to the cell value, and returns +/// `Ok(Ok(value_as_f64))` if successful or `Ok(Err(value_to_string))` if unsuccessful, +/// therefore never failing. This function is intended to be used with Serde's +/// [`deserialize_with`](https://serde.rs/field-attrs.html) field attribute. +pub fn deserialize_as_f64_or_string<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let data = Data::deserialize(deserializer)?; + Ok(data.as_f64().ok_or_else(|| data.to_string())) +} + +/// A helper function to deserialize cell values as `chrono::NaiveDate`, +/// useful when cells may also contain invalid values (i.e. strings). +/// It applies the [`as_date`] method to the cell value, and returns +/// `Ok(Some(value_as_date))` if successful or `Ok(None)` if unsuccessful, +/// therefore never failing. This function is intended to be used with Serde's +/// [`deserialize_with`](https://serde.rs/field-attrs.html) field attribute. +#[cfg(feature = "dates")] +pub fn deserialize_as_date_or_none<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let data = Data::deserialize(deserializer)?; + Ok(data.as_date()) +} + +/// A helper function to deserialize cell values as `chrono::NaiveDate`, +/// useful when cells may also contain invalid values (i.e. strings). +/// It applies the [`as_date`] method to the cell value, and returns +/// `Ok(Ok(value_as_date))` if successful or `Ok(Err(value_to_string))` if unsuccessful, +/// therefore never failing. This function is intended to be used with Serde's +/// [`deserialize_with`](https://serde.rs/field-attrs.html) field attribute. +#[cfg(feature = "dates")] +pub fn deserialize_as_date_or_string<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let data = Data::deserialize(deserializer)?; + Ok(data.as_date().ok_or_else(|| data.to_string())) +} + +/// A helper function to deserialize cell values as `chrono::NaiveTime`, +/// useful when cells may also contain invalid values (i.e. strings). +/// It applies the [`as_time`] method to the cell value, and returns +/// `Ok(Some(value_as_time))` if successful or `Ok(None)` if unsuccessful, +/// therefore never failing. This function is intended to be used with Serde's +/// [`deserialize_with`](https://serde.rs/field-attrs.html) field attribute. +#[cfg(feature = "dates")] +pub fn deserialize_as_time_or_none<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let data = Data::deserialize(deserializer)?; + Ok(data.as_time()) +} + +/// A helper function to deserialize cell values as `chrono::NaiveTime`, +/// useful when cells may also contain invalid values (i.e. strings). +/// It applies the [`as_time`] method to the cell value, and returns +/// `Ok(Ok(value_as_time))` if successful or `Ok(Err(value_to_string))` if unsuccessful, +/// therefore never failing. This function is intended to be used with Serde's +/// [`deserialize_with`](https://serde.rs/field-attrs.html) field attribute. +#[cfg(feature = "dates")] +pub fn deserialize_as_time_or_string<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let data = Data::deserialize(deserializer)?; + Ok(data.as_time().ok_or_else(|| data.to_string())) +} + +/// A helper function to deserialize cell values as `chrono::Duration`, +/// useful when cells may also contain invalid values (i.e. strings). +/// It applies the [`as_duration`] method to the cell value, and returns +/// `Ok(Some(value_as_duration))` if successful or `Ok(None)` if unsuccessful, +/// therefore never failing. This function is intended to be used with Serde's +/// [`deserialize_with`](https://serde.rs/field-attrs.html) field attribute. +#[cfg(feature = "dates")] +pub fn deserialize_as_duration_or_none<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let data = Data::deserialize(deserializer)?; + Ok(data.as_duration()) +} + +/// A helper function to deserialize cell values as `chrono::Duration`, +/// useful when cells may also contain invalid values (i.e. strings). +/// It applies the [`as_duration`] method to the cell value, and returns +/// `Ok(Ok(value_as_duration))` if successful or `Ok(Err(value_to_string))` if unsuccessful, +/// therefore never failing. This function is intended to be used with Serde's +/// [`deserialize_with`](https://serde.rs/field-attrs.html) field attribute. +#[cfg(feature = "dates")] +pub fn deserialize_as_duration_or_string<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let data = Data::deserialize(deserializer)?; + Ok(data.as_duration().ok_or_else(|| data.to_string())) +} + +/// A helper function to deserialize cell values as `chrono::NaiveDateTime`, +/// useful when cells may also contain invalid values (i.e. strings). +/// It applies the [`as_datetime`] method to the cell value, and returns +/// `Ok(Some(value_as_datetime))` if successful or `Ok(None)` if unsuccessful, +/// therefore never failing. This function is intended to be used with Serde's +/// [`deserialize_with`](https://serde.rs/field-attrs.html) field attribute. +#[cfg(feature = "dates")] +pub fn deserialize_as_datetime_or_none<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let data = Data::deserialize(deserializer)?; + Ok(data.as_datetime()) +} + +/// A helper function to deserialize cell values as `chrono::NaiveDateTime`, +/// useful when cells may also contain invalid values (i.e. strings). +/// It applies the [`as_datetime`] method to the cell value, and returns +/// `Ok(Ok(value_as_datetime))` if successful or `Ok(Err(value_to_string))` if unsuccessful, +/// therefore never failing. This function is intended to be used with Serde's +/// [`deserialize_with`](https://serde.rs/field-attrs.html) field attribute. +#[cfg(feature = "dates")] +pub fn deserialize_as_datetime_or_string<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let data = Data::deserialize(deserializer)?; + Ok(data.as_datetime().ok_or_else(|| data.to_string())) +}