I am trying to deserialize a field "name" into "firstname" and "lastname".
My struct:
struct User {
#[serde(rename = "name", deserialize_with = "deserialize_firstname")]
firstname: String,
#[serde(deserialize_with = "deserialize_lastname")]
#[serde(rename = "name")]
lastname: String,
}
And the deserialize function is the following:
fn deserialize_firstname<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: serde::Deserializer<'de>,
{
let s: &str = serde::Deserialize::deserialize(deserializer)?;
let re = Regex::new(r"^\W*([\w-]+)").unwrap();
let m = re.find(s);
match m {
Some(value) => Ok( value.as_str().to_string()),
None => Ok("".to_string()),
}
}
(it is about the same thing for lastname).
I get the following error:
err: DeserializeError { field: None, kind: Message("missing field `name`") }
My question: How to deserialize a field into two different fields?
Serde can flatten structs, making the Rust representation more nested than the serialized representation, but it can't unflatten, which you're trying to do here. Instead, you can do that yourself by annotating the entire struct. Here we have UserSerde
which reflects the serialized structure, and convert it to User
, which is what you're looking for. (playground)
#[derive(Debug, Deserialize)]
struct UserSerde {
name: Name,
}
#[derive(Debug, Deserialize)]
#[serde(try_from = "&str")]
struct Name {
first: String,
last: String,
}
impl<'a> TryFrom<&'a str> for Name {
type Error = &'static str;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
let re = regex::Regex::new(r"^\W*([\w-]+)\W+([\w-]+)\W*$").unwrap();
let Some(caps) = re.captures(value) else {
return Err("could not parse a first and last name");
};
Ok(Self {
first: caps[1].to_string(),
last: caps[2].to_string(),
})
}
}
#[derive(Debug, Deserialize)]
#[serde(from = "UserSerde")]
struct User {
first: String,
last: String,
// other fields...
}
impl From<UserSerde> for User {
fn from(value: UserSerde) -> Self {
let UserSerde {
name: Name { first, last },
} = value;
Self { first, last }
}
}
Note that if you're using this to store arbitrary human names, splitting them into first and last names is not a good idea. At least have a fallback that's just a normal String
.