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)?
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)),
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With