JSON deserialize custom internal struct using "with"



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)
    }
}