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

Serde Deserialize and Serialize impls #149

Open
yhtez opened this issue May 8, 2020 · 4 comments
Open

Serde Deserialize and Serialize impls #149

yhtez opened this issue May 8, 2020 · 4 comments

Comments

@yhtez
Copy link

yhtez commented May 8, 2020

Would you accept a PR that added serde Deserialize and Serialize implementations (behind a feature gate)?

@paholg
Copy link
Owner

paholg commented May 11, 2020

I'm not necessarily opposed to it, but curious what your use case is, and what that would look like in practice.

Since type annotations would be required for deserialization, would the deserializer try to parse a number, and just check whether it's the expected value for the type?

For serialization, do we just go to the number? In that case, is it really more convenient than serializing the associated constant?

De/serializing zero-sized types seems pretty strange to me, but I'm open to it if you have a use case that can't otherwise be ergonomically handled.

@yhtez
Copy link
Author

yhtez commented May 17, 2020

I'm not necessarily opposed to it, but curious what your use case is, and what that would look like in practice.

My use case was essentially something like this which would allow deserializing from a sequence such as a JSON array [opcode, ..data] but this would also be useful for a map with a numerical tag like {"_op": opcode, ..data}.

#[derive(Deserialize)]
#[serde(untagged)]
enum Msg {
    Subscribe { _op: U0, path: String, oneshot: bool },
    Unsubscribe { _op: U1, path: String },
}

// ...

// Msg::Subscribe { _op: Default::default(), path: "/somewhere/something".to_string(), oneshot: true }
let _: Msg = from_str("[0, \"/somewhere/something\", true]")?;
let _: Msg = from_str("[1, \"/somewhere_else/something_else\"]")?;

ideally this would be supported by renaming variants to a number but this currently isn't supported.

#[derive(Deserialize)]
#[serde(tag = "_op")]
enum Msg {
    #[rename = 0]
    Subscribe { path: String, oneshot: bool },
    #[rename = 1]
    Unsubscribe { path: String },
}

Unfortunately as far as I know there is no way to specify that serde should serialize as a sequence but this at least gets half of the way there by deriving a Deserialize impl and as I mentioned before is useful for map based data.

Since type annotations would be required for deserialization, would the deserializer try to parse a number, and just check whether it's the expected value for the type?

Yes:

assert!(from_str::<U0>("0").is_ok());
assert!(from_str::<U1>("0").is_err());

For serialization, do we just go to the number? In that case, is it really more convenient than serializing the associated constant?

Yes also and this allows for the example I showed with enum variants with the constant number being defined as part of the type for that specific variant.

De/serializing zero-sized types seems pretty strange to me, but I'm open to it if you have a use case that can't otherwise be ergonomically handled.

I admit this is an extremely strange idea but it seems to kind of make sense as a way to represent this, I'd be open for any suggestions of how to handle this otherwise without a handwritten deserialize impl.

@yhtez
Copy link
Author

yhtez commented May 17, 2020

I also made a simple implementation for a newtype wrapper of just unsigned ints to show what it could look like. (This probably could be optimized for (de)serializing to smaller sized ints with a Le trait bound but I haven't tried that)

use core::fmt;

use serde::{
    de::{self, Deserializer, Visitor},
    ser::Serializer,
    Deserialize, Serialize,
};

use typenum::marker_traits::Unsigned;

#[derive(Default)]
pub struct Wrapper<T>(pub T);
#[derive(Default)]
struct UnsignedVisitor<T>(T);
impl<'de, U: Unsigned> Visitor<'de> for UnsignedVisitor<U> {
    type Value = U;

    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_fmt(format_args!("{}", Self::Value::U64))
    }

    fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        if v == Self::Value::U64 {
            Ok(self.0)
        } else {
            Err(E::custom(format!(
                "expected {} but got {}",
                Self::Value::U64,
                v
            )))
        }
    }
}

impl<'de, U: Unsigned> Deserialize<'de> for Wrapper<U> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer
            .deserialize_u64(UnsignedVisitor::default())
            .map(Wrapper)
    }
}

impl<U: Unsigned> Serialize for Wrapper<U> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_u64(U::U64)
    }
}

@arthurgreef
Copy link

arthurgreef commented May 29, 2022

Hi - my use case for this feature is attempting to use serde to serialize/deserialize constants in the Neptune library. I get the following error:

the trait `Serialize` is not implemented for `UInt<UInt<UTerm, B1>, B0>`

Is this a valid use case?

generic-array serializes and deserializes generic arrays that use UInts but they serialize/derserialize outside of the typenum library.

Is that what you recommend is done?

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

No branches or pull requests

3 participants