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

Context-Dependent Serialization via Custom Field Attributes #2900

Open
cernec1999 opened this issue Feb 28, 2025 · 0 comments
Open

Context-Dependent Serialization via Custom Field Attributes #2900

cernec1999 opened this issue Feb 28, 2025 · 0 comments

Comments

@cernec1999
Copy link

cernec1999 commented Feb 28, 2025

Problem

Hello! I'd like to address an issue that has been raised numerous times in this repository. Namely, I'd like to propose context-dependent serialization based on certain attributes on fields in a struct.

Consider this simple example:

/// RcLogin packet.
#[derive(Debug, Serialize)]
pub struct RcLogin {
    /// Encryption key.
    pub encryption_key: u8,
    /// Version.
    pub version: String,
    /// Account.
    // TODO: This needs to be handled slightly differently by our custom serializer
    pub account: String,
    /// Password.
    // TODO: This needs to be handled slightly differently by our custom serializer
    pub password: String,
    /// PC IDs
    pub identification: Vec<String>,
}

The two fields above, account and password, are Strings encoded in a special format in the format I am writing the serializer for. More specifically, the custom serializer should encoded the length information of the string, but only for these two types. Everything else is "normal" serialization (i.e. just encoding the string with no additional information).

Currently, the way you can do this is by implementing serialize_with, deserialize_with, and with. The issue with this approach with respect to custom serializers is that as far as I'm aware, it's impossible for these functions to access the internal state of the custom serializer / deserializer. See: https://github.com/Preagonal/preagonal-client-rs/blob/253164d83aa52641d2dcaf066c0c7a1f650ab2b1/src/net/serialization/serialize.rs#L13-L15

The way I solved the above issue, and still getting the internal state of the serializer, is actually using a newtype (called GString) with some unsafe code, which is definitely not my preferred solution:

    fn serialize_newtype_struct<T>(
        self,
        name: &'static str,
        value: &T,
    ) -> Result<Self::Ok, Self::Error>
    where
        T: ?Sized + Serialize,
    {
        match name {
            "GString" => {
                // TODO(@ropguy): The following unsafe cast is used to extract a GString.
                let gstring: &GString = unsafe { &*(value as *const T as *const GString) };
                self.writer.write_gstring(&gstring.0)?;
                Ok(())
            }
            _ => todo!(),
        }
    }

And you might be wondering: why not impl Serialize for GString? As mentioned, I'm looking for a way to customize the serialization logic for only my serializer. If there was another serializer, like JSON, that wanted to serialize my type, it shouldn't be locked in to the GString serialization.

Previous Issues

There are a few other issues that have been raised in this repository with this as a feature request. Please see this comment on this closed issue: #2309 (comment)

In addition, this is a current open issue in Serde that has a similar request: #2877

Supporting XML-like documents is not a goal for serde. You would be better off using an XML-specific derive macro.

I understand not wanting to support XML-like documents, but something that is a global serialization / deserialization strategy should not be so opinionated when it comes to a quality-of-life feature such as custom attributes, especially when it doesn't just relate to XML documents.

Proposal

Like #2877, I'd like to propose a broader solution. Instead of adding namespace support, serde should allow custom attributes to be specified like so:

/// RcLogin packet.
#[derive(Debug, Serialize)]
pub struct RcLogin {
    /// Encryption key.
    pub encryption_key: u8,
    /// Version.
    pub version: String,
    /// Account.
    #[serde(attr = "gstring")]
    pub account: String,
    /// Password.
    #[serde(attr = "gstring")]
    pub password: String,
    /// PC IDs
    pub identification: Vec<String>,
}

Then, the custom serializer / deserializer will somehow be able to access the attr and perform more context-dependent serialization. In my case, I would forego

Other Libraries

I think the way Kotlin does things with its @SerialInfo annotation is a clean way to solve this: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-info/

Final Thoughts

Assuming the above aligns with the maintainer's design philosophy, I'm happy to file a PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

1 participant