CodeBlanks Logo Icon CodeBlanks

Summary - Serde

Combine serde_json and derive to build a config reader.

Serde summary

#[derive(Serialize, Deserialize)] defines the data structure independently of the format; a crate like serde_json provides the format (to_string, from_str). Names are mapped with rename/rename_all. Missing fields are tolerated with Option or #[serde(default)]; skip_serializing_if skips empty fields. Reading always returns a Result, and the error message names the missing or invalid field.

Round trip

Two functions that write a settings type and read it back from the same text:

#[derive(Debug, PartialEq)]
struct Ayar {
    port: u16,
    hata_ayikla: bool,
}

impl Ayar {
    fn json(&self) -> String {
        format!("{{\"port\":{},\"hata_ayikla\":{}}}", self.port, self.hata_ayikla)
    }

    fn oku(s: &str) -> Result<Ayar, String> { /* parse key by key */ }
}

The reader must convert each key by its type and give a clear error on an unknown key.

Practice Settings round trip

Write the json and oku methods for the Ayar type above (oku: drop the braces, split at commas, split_once(':'), parse by key). Print the JSON for Ayar { port: 8080, hata_ayikla: false }, then read it back and print with {:?}.

Starter code

main.rs
fn main() {
    // buraya yaz
}

Sample solution

main.rs
#[derive(Debug, PartialEq)]
struct Ayar {
    port: u16,
    hata_ayikla: bool,
}

impl Ayar {
    fn json(&self) -> String {
        format!("{{\"port\":{},\"hata_ayikla\":{}}}", self.port, self.hata_ayikla)
    }

    fn oku(s: &str) -> Result<Ayar, String> {
        let mut port = None;
        let mut hata_ayikla = None;
        for cift in s.trim().trim_start_matches('{').trim_end_matches('}').split(',') {
            let (a, d) = cift.split_once(':').ok_or("geçersiz çift")?;
            let deger = d.trim();
            match a.trim().trim_matches('"') {
                "port" => port = Some(deger.parse().map_err(|_| "geçersiz port")?),
                "hata_ayikla" => hata_ayikla = Some(deger.parse().map_err(|_| "geçersiz bool")?),
                k => return Err(format!("bilinmeyen anahtar: {k}")),
            }
        }
        Ok(Ayar {
            port: port.ok_or("port eksik")?,
            hata_ayikla: hata_ayikla.ok_or("hata_ayikla eksik")?,
        })
    }
}

fn main() {
    let a = Ayar { port: 8080, hata_ayikla: false };
    let metin = a.json();
    println!("{}", metin);
    println!("{:?}", Ayar::oku(&metin).unwrap());
}

Expected output

{"port":8080,"hata_ayikla":false}
Ayar { port: 8080, hata_ayikla: false }

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 Why is writing a config and reading it back to get the same value (round-trip) an important test?

  • It proves the writing and reading code agree with each other Correct answer
  • It measures performance
  • It isn't needed
  • It shrinks the file size

Explanation: It's especially valuable for hand-written serialization; even with derive it shows attributes like rename work correctly.

2 Read the boolean

Question
"hata_ayikla" => hata_ayikla = deger.____().map_err(|_| "geçersiz bool")?,
Answer
"hata_ayikla" => hata_ayikla = deger.parse().map_err(|_| "geçersiz bool")?,

Explanation: bool implements FromStr too: "true"/"false" are parsed.

3 Group summary

Term Meaning
to_string Writing
from_str Reading
rename_all Naming rule
skip_serializing_if Skip empty

All lessons in Intermediate