Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Further process a field using serde_json

Tags:

rust

serde

I'm using serde_json to decode push messages from GCP pub/sub.

#[derive(Debug, Deserialize, Serialize)]
pub struct PushMessage {
    pub message: Message,
    pub subscription: String,
}

In the messages, the payload is a base64-encoded string under the data key:

#[derive(Debug, Deserialize, Serialize)]
pub struct Message {
    message_id: String,
    #[serde(with = "base64data")]
    pub data: String,
}

… that I decode using a simple base64data module:

// A serializer/deserializer for base64-encoded data
mod base64data {
    use serde::{Deserialize, Serialize};
    use serde::{Deserializer, Serializer};

    use base64::engine::general_purpose::STANDARD as base64;
    use base64::engine::Engine as _;

    pub fn serialize<S: Serializer>(v: &str, s: S) -> Result<S::Ok, S::Error> {
        let b64 = base64.encode(v);
        String::serialize(&b64, s)
    }

    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<String, D::Error> {
        let b64 = String::deserialize(d)?;
        base64
            .decode(&b64)
            .map(|v| String::from_utf8(v).expect("Invalid UTF-8"))
            .map_err(|e| serde::de::Error::custom(e))
    }
}

However, I know the type I'm expecting to receive is also serialized as JSON (i.e., the base64-encoded string decodes to a JSON string), so ideally I would like to tell serde_json to keep processing the field, not just to decode the base64 data. Is this possible (e.g. by passing a generic parameter for the type we want to deserialize to)?

like image 636
beta Avatar asked Sep 14 '25 18:09

beta


1 Answers

It is problematic to inject it into the same JSON processing pipeline, because (a) not every serializer/deserializer is serde_json, and you need to be compatible with all of them, and (b) serde_json also allows writing to and reading from streams, not just strings.

Instead, just serialize/deserialize it inside the methods using serde_json:

// A serializer/deserializer for base64-encoded data
mod json_base64data {
    use serde::de::DeserializeOwned;
    use serde::{Deserialize, Serialize};
    use serde::{Deserializer, Serializer};

    use base64::engine::general_purpose::STANDARD as base64;
    use base64::engine::Engine as _;

    pub fn serialize<T: ?Sized + Serialize, S: Serializer>(v: &T, s: S) -> Result<S::Ok, S::Error> {
        let v = match serde_json::to_string(v) {
            Ok(v) => v,
            Err(e) => return Err(serde::ser::Error::custom(e)),
        };
        let b64 = base64.encode(v);
        String::serialize(&b64, s)
    }

    pub fn deserialize<'de, T: DeserializeOwned, D: Deserializer<'de>>(
        d: D,
    ) -> Result<T, D::Error> {
        let b64 = String::deserialize(d)?;
        match base64.decode(&b64) {
            Ok(v) => match serde_json::from_slice(&v) {
                Ok(v) => Ok(v),
                Err(e) => Err(serde::de::Error::custom(e)),
            },
            Err(e) => Err(serde::de::Error::custom(e)),
        }
    }
}
like image 152
Chayim Friedman Avatar answered Sep 17 '25 08:09

Chayim Friedman