- with
JSON deserialize custom internal struct using "with"
- 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.
examples/json/deserialize-to-internal-struct/Cargo.toml
[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"
examples/json/deserialize-to-internal-struct/data.json
{ "name": "Mr. Plow", "phone": "636-555-3226" }
examples/json/deserialize-to-internal-struct/src/main.rs
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) } }