serde_this_or_that/lib.rs
1#![doc(html_root_url = "https://docs.rs/serde-this-or-that/0.5.0")]
2#![warn(rust_2018_idioms, missing_docs)]
3#![deny(warnings, dead_code, unused_imports, unused_mut)]
4
5//! [![github]](https://github.com/rnag/serde-this-or-that) [![crates-io]](https://crates.io/crates/serde-this-or-that) [![docs-rs]](https://docs.rs/serde-this-or-that)
6//!
7//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
8//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
9//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
10//!
11//! <br>
12//!
13//! Custom deserialization for fields that can be specified as multiple types.
14//!
15//! <br>
16//!
17//! ## Usage
18//!
19//! ```rust
20//! use serde::Deserialize;
21//! use serde_json::from_str;
22//! use serde_this_or_that::{as_bool, as_f64, as_opt_i64, as_u64};
23//!
24//! #[derive(Deserialize, Debug)]
25//! #[serde(rename_all = "camelCase")]
26//! struct MyStruct {
27//! #[serde(deserialize_with = "as_bool")]
28//! is_active: bool,
29//! #[serde(deserialize_with = "as_u64")]
30//! num_attempts: u64,
31//! #[serde(deserialize_with = "as_f64")]
32//! grade: f64,
33//! // uses #[serde(default)] in case the field is missing in JSON
34//! #[serde(default, deserialize_with = "as_opt_i64")]
35//! confidence: Option<i64>,
36//! }
37//!
38//! fn main() -> Result<(), Box<dyn std::error::Error>> {
39//! let string = r#"
40//! {
41//! "isActive": "True",
42//! "numAttempts": "",
43//! "grade": "81",
44//! "confidence": "A+"
45//! }
46//! "#;
47//!
48//! let s: MyStruct = from_str(string)?;
49//! println!("{s:#?}");
50//!
51//! assert!(s.is_active);
52//! assert_eq!(s.num_attempts, 0);
53//! assert_eq!(s.grade, 81.0);
54//! assert_eq!(s.confidence, None);
55//!
56//! Ok(())
57//! }
58//! ```
59//!
60//! ## Examples
61//!
62//! You can check out sample usage of this crate in the [examples/](https://github.com/rnag/serde-this-or-that/tree/main/examples)
63//! folder in the project repo on GitHub.
64//!
65//! ## Performance
66//!
67//! The benchmarks suggest that implementing a custom
68//! [`Visitor`] as `serde-this-or-that` does, performs
69//! on average **about 15x better** than an approach with an [untagged enum].
70//!
71//! A separate benchmark compares performance against the [serde_with] crate:
72//! it indicates both crates perform about the same,
73//! assuming only [`DisplayFromStr`] is used.
74//! But when [`PickFirst`] is involved, `serde-this-or-that` appears to
75//! perform **about 12x better** in an average case.
76//!
77//! The benchmarks live in the [benches/](https://github.com/rnag/serde-this-or-that/tree/main/benches)
78//! folder, and can be run with `cargo bench`.
79//!
80//! [`Visitor`]: https://docs.serde.rs/serde/de/trait.Visitor.html
81//! [untagged enum]: https://stackoverflow.com/a/66961340/10237506
82//! [serde_with]: https://docs.rs/serde_with
83//! [`DisplayFromStr`]: https://docs.rs/serde_with/latest/serde_with/struct.DisplayFromStr.html
84//! [`PickFirst`]: https://docs.rs/serde_with/latest/serde_with/struct.PickFirst.html
85//!
86//! ## Optionals
87//!
88//! The extra helper functions that begin with `as_opt`, return an `Option<T>` of the respective data type `T`,
89//! rather than the type `T` itself (see [#4](https://github.com/rnag/serde-this-or-that/issues/4)).
90//!
91//! On success, they return a value of `T` wrapped in [`Some`].
92//!
93//! On error, or when there is a `null` value, or one of an *invalid* data type, the
94//! `as_opt` helper functions return [`None`] instead.
95//!
96//!
97//! ## Readme Docs
98//!
99//! You can find the crate's readme documentation on the
100//! [crates.io] page, or alternatively in the [`README.md`] file on the GitHub project repo.
101//!
102//! [crates.io]: https://crates.io/crates/serde-this-or-that
103//! [`README.md`]: https://github.com/rnag/serde-this-or-that
104//!
105
106mod de_impl;
107mod de_impl_opt;
108
109pub use de_impl::{as_bool, as_f64, as_i64, as_string, as_u64};
110pub use de_impl_opt::{as_opt_bool, as_opt_f64, as_opt_i64, as_opt_string, as_opt_u64};
111#[doc(hidden)]
112pub use serde;
113#[doc(hidden)]
114pub use serde::*;
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 use serde::Deserialize;
121 use serde_json::from_str;
122
123 #[test]
124 fn serde_this_or_that_works() {
125 #[derive(Deserialize)]
126 #[serde(rename_all = "camelCase")]
127 struct MyStruct {
128 #[serde(deserialize_with = "as_bool")]
129 is_active: bool,
130 #[serde(deserialize_with = "as_u64")]
131 num_attempts: u64,
132 }
133
134 let string = r#"
135 {
136 "isActive": "True",
137 "numAttempts": "3"
138 }
139 "#;
140
141 let s: MyStruct = from_str(string).unwrap();
142
143 assert!(s.is_active);
144 assert_eq!(s.num_attempts, 3)
145 }
146}