Skip to content

Commit

Permalink
Merge pull request #5 from rnag/WIP-export-as-optional-functions
Browse files Browse the repository at this point in the history
Export `as_opt_<type>` helper functions
  • Loading branch information
rnag authored Jan 25, 2025
2 parents d9ca3bc + 4ea42a5 commit 0bc6bc7
Show file tree
Hide file tree
Showing 13 changed files with 1,745 additions and 59 deletions.
43 changes: 38 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,43 @@ Possible header types:
- `Breaking Changes` for any backwards-incompatible changes.

## [Unreleased]

<!--
### Features
- Added a new struct `MyStruct` with the following methods:
- `my_method()`
- `other_method()`
-->

## v0.5.0 (2025-01-25)

### Features

- **Added support for optional deserialization**:
- New functions were introduced to handle `Option<T>` types during deserialization:
- `as_opt_bool` – Returns `Option<bool>`.
- `as_opt_f64` – Returns `Option<f64>`.
- `as_opt_i64` – Returns `Option<i64>`.
- `as_opt_string` – Returns `Option<String>`.
- `as_opt_u64` – Returns `Option<u64>`.

These functions ensure that `null` values in the JSON input or deserialization errors are correctly deserialized into
`None`,
while valid values will
return the appropriate `Some(T)`.

### Bug Fixes

- Resolved an issue where the existing deserialization functions did not handle `Option<T>` types, only direct types.
Now,
`null` values and errors are deserialized to `None`, making the handling of nulls more consistent with Serde's
standard
behavior.

### Breaking Changes

- No breaking changes introduced in this version.

## v0.4.2 (2023-02-05)

### Bug Fixes
Expand All @@ -32,24 +62,27 @@ Possible header types:
## v0.4.0 (2022-04-18)

### Features

- Add benchmarks to compare performance against `serde_with`.
- Flatten some nested `match` arms into simpler `if` statements.
- Update `as_bool`
- Update to check for a new "truthy" string value of `ON`.
- Add pattern matching to check common *true/false* values **before** converting the string
to uppercase, which should make it overall more efficient.
- Update to check for a new "truthy" string value of `ON`.
- Add pattern matching to check common *true/false* values **before** converting the string
to uppercase, which should make it overall more efficient.
- `serde_this_or_that` is now on par - in terms of performance - with `serde_with`! This is
truly great news.

## v0.3.0 (2022-04-17)

### Breaking Changes

- Remove dependency on the `derive` feature of `serde`
- Add it as an optional feature named `derive` instead.
- Add it as an optional feature named `derive` instead.

### Features

- Replace `utilities` keyword with `this-or-that`, as I want crate to be
searchable when someone types "this or that".
searchable when someone types "this or that".
- Update docs.

## v0.2.0 (2022-04-17)
Expand Down
46 changes: 33 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
[package]
name = "serde-this-or-that"
version = "0.4.2"
authors = ["Ritvik Nag <rv.kvetch@gmail.com>"]
version = "0.5.0"
authors = ["Ritvik Nag <me@ritviknag.com>"]
description = "Custom deserialization for fields that can be specified as multiple types."
documentation = "https://docs.rs/serde-this-or-that"
repository = "https://github.com/rnag/serde-this-or-that"
readme = "README.md"
keywords = ["serde",
# I would have liked to add the below keyword, but of course
# crates.io has a limit of 5 keywords, so well.. that's that. :\
# "utilities",
# And now surprisingly, I found this keyword to be super useful!
# I'm actually partially shocked that crates.io doesn't use the
# crate name to automagically satisfy user search requests.
"this-or-that",
# Of course, the rest that follow are also pretty solid too.
"deserialization",
"visitor",
"multiple-type"]
# I would have liked to add the below keyword, but of course
# crates.io has a limit of 5 keywords, so well.. that's that. :\
# "utilities",
# And now surprisingly, I found this keyword to be super useful!
# I'm actually partially shocked that crates.io doesn't use the
# crate name to automagically satisfy user search requests.
"this-or-that",
# Of course, the rest that follow are also pretty solid too.
"deserialization",
"visitor",
"multiple-type"]
categories = ["encoding"]
license = "MIT"
edition = "2021"
Expand Down Expand Up @@ -82,3 +82,23 @@ path = "benches/as_bool.rs"
name = "serde_with"
harness = false
path = "benches/serde_with.rs"

[[example]]
name = "opt_as_bool"
path = "examples/optionals/as_bool.rs"

[[example]]
name = "opt_as_f64"
path = "examples/optionals/as_f64.rs"

[[example]]
name = "opt_as_i64"
path = "examples/optionals/as_i64.rs"

[[example]]
name = "opt_as_string"
path = "examples/optionals/as_string.rs"

[[example]]
name = "opt_as_u64"
path = "examples/optionals/as_u64.rs"
45 changes: 36 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This crate works with Cargo with a `Cargo.toml` like:

```toml
[dependencies]
serde-this-or-that = "0.4"
serde-this-or-that = "0.5"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
```
Expand All @@ -27,7 +27,7 @@ Here's an example of using `serde-this-or-that` in code:
```rust
use serde::Deserialize;
use serde_json::from_str;
use serde_this_or_that::{as_bool, as_f64, as_u64};
use serde_this_or_that::{as_bool, as_f64, as_opt_i64, as_u64};

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
Expand All @@ -38,14 +38,18 @@ struct MyStruct {
num_attempts: u64,
#[serde(deserialize_with = "as_f64")]
grade: f64,
// uses #[serde(default)] in case the field is missing in JSON
#[serde(default, deserialize_with = "as_opt_i64")]
confidence: Option<i64>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
let string = r#"
{
"isActive": "True",
"numAttempts": "",
"grade": "81"
"grade": "81",
"confidence": "A+"
}
"#;

Expand All @@ -55,22 +59,29 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
assert!(s.is_active);
assert_eq!(s.num_attempts, 0);
assert_eq!(s.grade, 81.0);
assert_eq!(s.confidence, None);

Ok(())
}
```

## Exported Functions

- [`as_bool`](https://docs.rs/serde-this-or-that/latest/serde_this_or_that/fn.as_bool.html)
- [`as_f64`](https://docs.rs/serde-this-or-that/latest/serde_this_or_that/fn.as_f64.html)
- [`as_i64`](https://docs.rs/serde-this-or-that/latest/serde_this_or_that/fn.as_i64.html)
- [`as_string`](https://docs.rs/serde-this-or-that/latest/serde_this_or_that/fn.as_string.html)
- [`as_u64`](https://docs.rs/serde-this-or-that/latest/serde_this_or_that/fn.as_u64.html)
- [`as_bool`](https://docs.rs/serde-this-or-that/latest/serde_this_or_that/fn.as_bool.html) / [
`as_opt_bool`](https://docs.rs/serde-this-or-that/latest/serde_this_or_that/fn.as_opt_bool.html)
- [`as_f64`](https://docs.rs/serde-this-or-that/latest/serde_this_or_that/fn.as_f64.html) / [
`as_opt_f64`](https://docs.rs/serde-this-or-that/latest/serde_this_or_that/fn.as_opt_f64.html)
- [`as_i64`](https://docs.rs/serde-this-or-that/latest/serde_this_or_that/fn.as_i64.html) / [
`as_opt_i64`](https://docs.rs/serde-this-or-that/latest/serde_this_or_that/fn.as_opt_i64.html)
- [`as_string`](https://docs.rs/serde-this-or-that/latest/serde_this_or_that/fn.as_string.html) / [
`as_opt_string`](https://docs.rs/serde-this-or-that/latest/serde_this_or_that/fn.as_opt_string.html)
- [`as_u64`](https://docs.rs/serde-this-or-that/latest/serde_this_or_that/fn.as_u64.html) / [
`as_opt_u64`](https://docs.rs/serde-this-or-that/latest/serde_this_or_that/fn.as_opt_u64.html)

## Examples

You can check out sample usage of this crate in the [examples/](https://github.com/rnag/serde-this-or-that/tree/main/examples)
You can check out sample usage of this crate in
the [examples/](https://github.com/rnag/serde-this-or-that/tree/main/examples)
folder in the project repo on GitHub.

## Performance
Expand All @@ -89,11 +100,26 @@ The benchmarks live in the [benches/](https://github.com/rnag/serde-this-or-that
folder, and can be run with `cargo bench`.

[`Visitor`]: https://docs.serde.rs/serde/de/trait.Visitor.html

[untagged enum]: https://stackoverflow.com/a/66961340/10237506

[serde_with]: https://docs.rs/serde_with

[`DisplayFromStr`]: https://docs.rs/serde_with/latest/serde_with/struct.DisplayFromStr.html

[`PickFirst`]: https://docs.rs/serde_with/latest/serde_with/struct.PickFirst.html

## Optionals

The extra helper functions that begin with `as_opt`, return an `Option<T>` of the respective data type `T`,
rather than the type `T` itself (see [#4](https://github.com/rnag/serde-this-or-that/issues/4)).

On success, they return a value of `T` wrapped in [
`Some`](https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some).

On error, or when there is a `null` value, or one of an *invalid* data type, the
`as_opt` helper functions return [`None`](https://doc.rust-lang.org/std/option/enum.Option.html#variant.None) instead.

## Contributing

Contributions are welcome! Open a pull request to fix a bug, or [open an issue][]
Expand All @@ -102,6 +128,7 @@ to discuss a new feature or change.
Check out the [Contributing][] section in the docs for more info.

[Contributing]: CONTRIBUTING.md

[open an issue]: https://github.com/rnag/serde-this-or-that/issues

## License
Expand Down
38 changes: 19 additions & 19 deletions examples/as_bool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use serde_this_or_that::as_bool;
#[derive(Clone, Debug, Deserialize)]
pub struct Msg {
#[serde(deserialize_with = "as_bool")]
pub timestamp: bool,
pub archived: bool,
}

// A simple type alias so as to DRY.
Expand All @@ -22,99 +22,99 @@ fn main() -> Result<()> {
trace!("With Empty String:");
let data = r#"
{
"timestamp": ""
"archived": ""
}"#;

let m: Msg = serde_json::from_str(data).unwrap();
assert!(!m.timestamp);
assert!(!m.archived);
trace!(" {m:?}");

trace!("With Null: ");
let data = r#"
{
"timestamp": null
"archived": null
}"#;

let m: Msg = serde_json::from_str(data).unwrap();
assert!(!m.timestamp);
assert!(!m.archived);
trace!(" {m:?}");

trace!("With Zero (0):");
let data = r#"
{
"timestamp": 0
"archived": 0
}"#;

let m: Msg = serde_json::from_str(data).unwrap();

trace!(" {m:?}");
assert!(!m.timestamp);
assert!(!m.archived);

trace!("With One (1):");

let data = r#"
{
"timestamp": 1
"archived": 1
}"#;

let m: Msg = serde_json::from_str(data).unwrap();

trace!(" {m:?}");
assert!(m.timestamp);
assert!(m.archived);

trace!("With String (truthy #1):");

let data = r#"
{
"timestamp": "tRuE"
"archived": "tRuE"
}"#;

let m: Msg = serde_json::from_str(data).unwrap();

trace!(" {m:?}");
assert!(m.timestamp);
assert!(m.archived);

trace!("With String (truthy #2):");

let data = r#"
{
"timestamp": "Y"
"archived": "Y"
}"#;

let m: Msg = serde_json::from_str(data).unwrap();

trace!(" {m:?}");
assert!(m.timestamp);
assert!(m.archived);

trace!("With String (falsy):");

let data = r#"
{
"timestamp": "nope!"
"archived": "nope!"
}"#;

let m: Msg = serde_json::from_str(data).unwrap();

trace!(" {m:?}");
assert!(!m.timestamp);
assert!(!m.archived);

trace!("With String (Invalid Numeric):");

let data = r#"
{
"timestamp": "123456789076543210"
"archived": "123456789076543210"
}"#;

let m: Msg = serde_json::from_str(data).unwrap();

trace!(" {m:?}");
assert!(!m.timestamp);
assert!(!m.archived);

trace!("With U64:");

let data = r#"
{
"timestamp": 123456789076543210
"archived": 123456789076543210
}"#;

if let Err(e) = serde_json::from_str::<Msg>(data) {
Expand All @@ -128,7 +128,7 @@ fn main() -> Result<()> {

let data = r#"
{
"timestamp": -123
"archived": -123
}"#;

if let Err(e) = serde_json::from_str::<Msg>(data) {
Expand Down
Loading

0 comments on commit 0bc6bc7

Please sign in to comment.