Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a custom deserializer instead of wrapper types #6

Merged
merged 51 commits into from
Nov 21, 2023
Merged

Conversation

romac
Copy link
Member

@romac romac commented Oct 26, 2023

No description provided.

@rnbguy
Copy link
Member

rnbguy commented Nov 19, 2023

To document how I solved the num_bigin::BigInt deserialization.

The ITF trace deserialization to native types works as follows,

  1. deserialize raw itf JSON to serde_json::Value
  2. then deserializing serde_json::Value to itf::value::Value (impl<'de> Deserialize<'de> for itf::value::Value)
  3. then deserializing itf::value::Value to T (impl<'de> Deserializer<'de> for itf::value::Value)

To get native deserialization of num_bigint::BigInt, itf::value::Value::BigInt needs to be deserialized as (int, [uint]). I added a56363c for this. And, 92bb9d3 allows itf::value::Value::BigInt to be deserialized as a tuple.

But this creates an weird issue - itf::from_str::<itf::value::Value> will deserialize {"#bigint": "99"} as [1, [99]]. As itf::from_str will deserialize itf-bigint format to itf::value::Value and then again to itself. The second deserialization makes it a tuple and itf::value::Value does not have any information on how to deserialize it back to itf::value::Value::BigInt. So I added c03bec1.

Note, with this, itf::from_str does not differentiate between {"#bigint": "99"} and [1, [99]]; they both can be deserialized to num_bigint::BigInt or any native integer types.

@rnbguy
Copy link
Member

rnbguy commented Nov 19, 2023

Note, this does not support num_bigint::BigUint, which has a different deserialization format than num_bigint::BigInt.

  • BigInt: (int, [uint])
  • BigUint: [uint]

This is definitely feasible. But probably, not necessary as ITF doesn't include unsigned integers.

@rnbguy
Copy link
Member

rnbguy commented Nov 19, 2023

Value::deserialize(Value) is creating equivalency issues for Value::{Set, Map, List} too. I added testcases in d89d2fb.

I suggest making itf::Value private. This way we can also revert c03bec1 🙂

@romac
Copy link
Member Author

romac commented Nov 20, 2023

Amazing stuff @rnbguy! Thanks so much!

I suggest making itf::Value private. This way we can also revert c03bec1 🙂

Yeah let's do that!

romac and others added 5 commits November 20, 2023 10:21
We keep exporting it to allow for usecases where one needs the raw ITF `Value`,
but avoid documenting it as there are a few pitfalls when doing so.

See <#6 (comment)> for more information
itf/src/de.rs Outdated
@@ -258,6 +258,7 @@ impl<'de> Deserializer<'de> for Value {
{
match self {
Value::BigInt(v) => visit_bigint(v, visitor),
Value::Number(v) => visit_bigint(BigInt::new(v), visitor),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea!

@rnbguy
Copy link
Member

rnbguy commented Nov 20, 2023

@romac any idea why this (9920493) is not working?

let itf = serde_json::json!({
"_foo": {"#bigint": "1"},
"typ": "Foo",
});
#[derive(Deserialize, Debug)]
#[serde(tag = "typ")]
enum FooBarInt {
// try to deserialize _foo as i64, instead of BigInt
Foo { _foo: i64 },
Bar { _bar: String },
}
assert!(itf::from_value::<FooBarInt>(itf.clone()).is_err());

Update: I realized why ! In this case, deserialize_any is being called - which always deserializes #bigint to num_bigint::BigInt.

Value::BigInt(v) => visit_bigint(v, visitor),

IMO, this requires something like a TryVisitor trait, which allows one to visit an itf::Value multiple times until success. I am not sure if it's possible. Otherwise, we should not let users deserialize #bigint to i64 to avoid confusion.

@romac
Copy link
Member Author

romac commented Nov 20, 2023

Ah good catch! Yeah it's a shame we can't seem to support this.

That said, we can perhaps still enable this by providing a from_bigint<'de, A: TryFrom<BigInt>, D>(d: D) -> Result<A, D::Error> helper function which could be used with #[serde(deserialize_with = "itf::de::from_bigint")].

See a2eef53

What do you think?

@rnbguy
Copy link
Member

rnbguy commented Nov 20, 2023

Yea serde(with) is the last straw 😅

Btw. I would prefer serde_with::DeserializeAs with serde_with::As. This adds a dependency to serde_with. But this way, we can support container types too.

At itf-rs crate,

impl<'de, T> serde_with::DeserializeAs<'de, T> for itf::value::BigInt
where
    T: TryFrom<num_bigint::BigInt>
{ ... }

Usage,

use itf::value::BigInt;
use serde_with::As;

#[derive(Deserialize, Debug)]
enum FooBarWithInt {
    Foo {
        #[serde(with = "As::<Vec<Vec<BigInt>>>")]
        _foo: Vec<Vec<i64>>,
    },
    Bar {
        #[serde(with = "As::<HashMap<BigInt, BigInt>>")]
        _bar: HashMap<i64, i64>,
    },
}

I can take care of the changes tomorrow 🙂

@romac
Copy link
Member Author

romac commented Nov 20, 2023

Ah good point, didn't think of containers! I am fine with adding serde-with as a dep :)

@rnbguy
Copy link
Member

rnbguy commented Nov 21, 2023

I removed the code that implicitly serialized BigInt to (u)int and vice-versa to avoid ambiguity while deserialization (see #6 (comment)). Now, {"#bigint": "<num>"} is always deserialized to BigInt.

For Rust primitive integer types, a user has to explicitly use serde-with with serde_with::As (e.g. #[serde(with = "As::<itf::de::Integer>")] in #6 (comment)) or serde_with::serde_as attribute accordingly.

@rnbguy
Copy link
Member

rnbguy commented Nov 21, 2023

Btw, serde_with provides TryFromInto - which can be used in our context as follows - #[serde(with = "As::<TryFromInto<BigInt>>")]. itf::de::Integer is just a convenient shorthand for the same.

@romac
Copy link
Member Author

romac commented Nov 21, 2023

Awesome!

@romac romac marked this pull request as ready for review November 21, 2023 13:29
@rnbguy
Copy link
Member

rnbguy commented Nov 21, 2023

maybe it makes sense to redefine Integer as just

pub type Integer = TryFromInto<BigInt>;

@romac romac merged commit 9902045 into main Nov 21, 2023
5 checks passed
@romac romac deleted the native-types branch November 21, 2023 14:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants