Summary - Serde
- Rust course
- Intermediate
- Lesson 106/115
- 5 min
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
fn main() {
// buraya yaz
}Sample solution
#[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
"hata_ayikla" => hata_ayikla = deger.____().map_err(|_| "geçersiz bool")?,
"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 |