2025-11-05 || Tags: rust quick reminder programming
Writing a custom deserializer can be very helpful. Often I already have a specific struct that I want to use that only matches 90% to the struct I'm trying to deserialize. Here's a quick way on how to do it.
This is for use with rust's serde library. Seems to be the go to for serialization (and de-) in rust.
NOTE: I am a very new rust developer, do not take any of this as any gospel. Don't ask me about the de
stuff for example.
Here's a minimal example which seems to come up a lot for me.
use serde::Deserialize;
use serde::de::Deserializer;
fn parse_doc_id<'de, D>(deserializer: D) -> Result<PatentDetails, D::Error>
where
D: Deserializer<'de>,
{
// These 2 structures describe the format of JSON I'm receiving
#[derive(Debug, Deserialize)]
pub struct RegDocumentId {
#[serde(rename(deserialize = "reg:country"))]
pub reg_country: DollarValue,
#[serde(rename(deserialize = "reg:doc-number"))]
pub reg_doc_number: DollarValue,
#[serde(rename(deserialize = "reg:date"))]
pub reg_date: Option<DollarValue>,
}
#[derive(Debug, Deserialize)]
pub struct DollarValue {
#[serde(rename(deserialize = "$"))]
pub value: String,
}
// Deserialize it
let reg_doc_id = RegDocumentId::deserialize(deserializer)?;
// Do whatever custom stuff you want
let date = reg_doc_id
.reg_date
.map(|d| NaiveDate::parse_from_str(&d.value, "%Y%m%d").unwrap());
// Return the struct I want
Ok(PatentDetails {
country_code: reg_doc_id.reg_country.value.clone(),
number: reg_doc_id.reg_doc_number.value.clone(),
date,
kind_code: None,
reference_type: PatentReferenceType::Unknown,
})
}
#[derive(Debug, Deserialize)]
pub struct RegApplicationReference {
#[serde(
rename(deserialize = "reg:document-id"), // Because the JSON has a weird field key
deserialize_with = "parse_doc_id" // Note same function name as above
)]
pub reg_document_id: PatentDetails,
}
/// In some other part of your project
///...
let doc_ref: RegApplicationReference =
serde_json::from_reader(reader).expect("JSON was not well formatted");
// Should be in your nice "custom" data type now
let blah = doc_ref.do_something_youve_implemented_on_that_struct_previously();
Hopefully this is enough of an example to let you know what to do next.
When writing some code, I'll design structures that feel right to me for my use case first (or at
least early on). I will have written some impl and other supporting stuff for that struct too.
Then, later, I'll look at how the data comes in more specifically. Usually there's a 90% match,
but not 100%.
Not sure it's the best or correct way to do development (certainly something to be said for keeping deserializing completely separate from application logic/code), but it's comfortable for me.
I can put those annoying intermediate structures away in it's own separate function meaning less "clutter" when trying to grok the code - 95% of the time you won't need to look into this function and understand those intermediate structs. Hopefully.
As mentioned, it makes the deser stuff a bit leaky. The deser module/file will need to look at things that probably should be considered a bit more "parent".
Serialization is separated completely - you might need to write a serialize to get back to the original JSON format, including those annoying intermediate ones.
Optimisation: There's more logic, more branches, more stuff going on in what might need to be (or should try to be the quickest path).