What if the JSON contains phone numbers with area code as a single string, but we would like to represent the phone number as a struct with two fields "area" and "number"?
We can tell serde to deserialize this field with a custom function using the with
attribute.
[package]
name = "deserialize-to-internal-struct"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
{
"name": "Mr. Plow",
"phone": "636-555-3226"
}
use serde::Deserialize;
#[allow(unused)]
#[derive(Debug, Deserialize, PartialEq)]
struct Person {
name: String,
phone: String,
}
fn main() {
load_data_to_person();
parse_to_phone();
load_data_to_deep_person();
}
fn load_data_to_person() {
let path = "data.json";
let content = std::fs::read_to_string(path).unwrap();
let person = serde_json::from_str::<Person>(&content).unwrap();
println!("person {:?}", person);
assert_eq!(
person,
Person {
name: String::from("Mr. Plow"),
phone: String::from("636-555-3226")
}
);
}
fn parse_to_phone() {
let content = r#"{"area": "123", "number": "456-789"}"#.to_string();
let phone = serde_json::from_str::<Phone>(&content).unwrap();
println!("phone {:?}", phone);
assert_eq!(
phone,
Phone {
area: String::from("123"),
number: String::from("456-789")
}
);
}
fn load_data_to_deep_person() {
let path = "data.json";
let content = std::fs::read_to_string(path).unwrap();
let deep_person = serde_json::from_str::<DeepPerson>(&content).unwrap();
println!("deep_person {:?}", deep_person);
assert_eq!(
deep_person,
DeepPerson {
name: String::from("Mr. Plow"),
phone: Phone {
area: String::from("636"),
number: String::from("555-3226")
}
}
);
}
#[allow(unused)]
#[derive(Debug, Deserialize, PartialEq)]
struct Phone {
area: String,
number: String,
}
#[allow(unused)]
#[derive(Debug, Deserialize, PartialEq)]
struct DeepPerson {
name: String,
#[serde(with = "from_full_phone")]
phone: Phone,
}
mod from_full_phone {
use serde::{Deserialize, de};
use super::Phone;
pub fn deserialize<'de, D>(deserializer: D) -> Result<Phone, D::Error>
where
D: de::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
//println!("s {:?}", s);
let (area, number) = s
.split_once('-')
.ok_or(de::Error::custom("invalid phone"))?;
let p = Phone {
area: area.to_owned(),
number: number.to_owned(),
};
//println!("phone {:?}", p);
Ok(p)
}
}