Rust serde — 구조체와 JSON 을 오가는 표준
serde 는 Rust 구조체를 JSON 같은 형식으로 바꾸고(직렬화) 되돌리는(역직렬화) 사실상의 표준이다. #[derive(Serialize, Deserialize)] 한 줄이면 되고, 잘못된 JSON 은 어느 칸이 왜 틀렸는지 오류로 알려 준다. rename_all·default·skip_serializing_if 같은 속성으로 JSON 의 모양을 맞춘다.
01 붙이는 법
serde 는 바꾸는 틀을, serde_json 은 JSON 형식을 맡는다. derive 기능을 켜야 #[derive(Serialize)] 를 쓸 수 있다.
[package]
name = "notes"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "=1.0.229", features = ["derive"] }
serde_json = "=1.0.154"02 구조체 → JSON → 구조체
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Note {
title: String,
tags: Vec<String>,
pinned: bool,
}
fn main() {
let note = Note {
title: "Learn serde".to_string(),
tags: vec!["rust".to_string(), "json".to_string()],
pinned: true,
};
let json = serde_json::to_string_pretty(¬e).unwrap();
println!("{json}");
let back: Note = serde_json::from_str(&json).unwrap();
println!("{back:?}");
} Running `target/debug/notes`
{
"title": "Learn serde",
"tags": [
"rust",
"json"
],
"pinned": true
}
Note { title: "Learn serde", tags: ["rust", "json"], pinned: true }#[derive(Serialize, Deserialize)]는 입문 22편의#[derive(Debug)]처럼 바꾸는 코드를 컴파일러가 대신 쓰게 한다. 칸 이름이 그대로 JSON 키가 된다.serde_json::to_string_pretty는 줄을 바꿔 보기 좋게,to_string은 한 줄로 만든다.serde_json::from_str은 거꾸로 JSON 을 구조체로 읽는다. 무엇으로 읽을지let back: Note처럼 타입을 적는다(입문 30편의parse와 같다).- 둘 다
Result를 돌려준다. 여기서는 예제를 짧게 하려고unwrap했다.
03 잘못된 JSON 은 이유를 말해 준다
밖에서 들어오는 JSON 은 믿을 수 없다. 세 가지 입력을 읽어 보자.
use serde::Deserialize;
#[derive(Deserialize)]
struct Note {
title: String,
pinned: bool,
}
fn main() {
let inputs = [
r#"{"title": "ok", "pinned": false}"#,
r#"{"title": "no pin"}"#,
r#"{"title": "bad", "pinned": "yes"}"#,
];
for input in inputs {
match serde_json::from_str::<Note>(input) {
Ok(note) => println!("ok: {} (pinned: {})", note.title, note.pinned),
Err(e) => println!("error: {e}"),
}
}
} Running `target/debug/notes`
ok: ok (pinned: false)
error: missing field `pinned` at line 1 column 19
error: invalid type: string "yes", expected a boolean at line 1 column 32- 첫째는 맞다.
- 둘째는
pinned칸이 없다(missing field). 몇째 줄 몇째 글자에서 알아챘는지도 적는다. - 셋째는
pinned에"yes"라는 문자열이 왔는데bool이어야 한다(invalid type).
구조체가 곧 검사 규칙이다. 칸을 하나하나 꺼내 타입을 확인하는 코드를 쓰지 않아도, 모양이 다르면 Err 가 온다. r#"…"# 는 큰따옴표가 든 문자열을 백슬래시 없이 쓰는 문법(raw string)이다.
04 실습 — 속성으로 모양 맞추기
API 는 키를 isPinned 처럼 쓰는데 Rust 는 is_pinned 로 쓰는 경우가 많다. 칸이 없을 때의 기본값, 비어 있으면 빼기도 자주 필요하다.
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Note {
title: String,
#[serde(default)]
is_pinned: bool,
#[serde(skip_serializing_if = "Option::is_none")]
due_date: Option<String>,
}
fn main() {
let note: Note = serde_json::from_str(r#"{"title": "draft"}"#).unwrap();
println!("{note:?}");
println!("{}", serde_json::to_string(¬e).unwrap());
let with_due = Note { due_date: Some("2026-11-30".to_string()), ..note };
println!("{}", serde_json::to_string(&with_due).unwrap());
} Running `target/debug/notes`
Note { title: "draft", is_pinned: false, due_date: None }
{"title":"draft","isPinned":false}
{"title":"draft","isPinned":false,"dueDate":"2026-11-30"}#[serde(rename_all = "camelCase")]— 모든 칸 이름을 camelCase 로 바꿔 읽고 쓴다.is_pinned↔isPinned.#[serde(default)]— JSON 에 칸이 없으면 그 타입의 기본값(bool은false)을 쓴다. 위 둘째 예처럼 오류가 나지 않는다.#[serde(skip_serializing_if = "Option::is_none")]—None이면 JSON 에서 아예 뺀다. 둘째 줄에는dueDate가 없고, 값을 넣은 셋째 줄에는 있다.Note { due_date: …, ..note }는 나머지 칸을note에서 가져와 새 구조체를 만드는 문법이다.
Note 에 tags: Vec<String> 를 더하고 #[serde(default)] 를 붙여, 태그가 없는 JSON 도 읽히는지 확인해 보자. 다음 편은 명령줄 옵션을 맡는 clap 이다.