Serialization and Deserialization
Different programming languages store data in their own native formats:
- JavaScript → Objects
- Rust → Structs
- Python → Dicts
- Go → Structs These formats live in memory and are language-specific. You can’t just send a JS object over a network or any boundary to another system that wouldnt understand js object such as a rust or go server. There we agree on a neutral, language-agnostic format. Both sides can convert to/from this format. This process is called serialization and deserialization.
Serialization
This is the act of flattening your in-memory, language specific data structure into a transferrable sequence of bytes. The client converts its native data into agreed format before sending it.
const user = {
name: "quantinium",
age: 21,
scores: [98, 87, 91],
active: true
};
// Serialization → convert to a JSON string (bytes, language-agnostic)
const payload = JSON.stringify(user);
// '{"name":"quantinium","age":21,"scores":[98,87,91],"active":true}'
fetch("/api/user", {
method: "POST",
body: payload
});
Serialization Standards
You can use anything as a transfer format as long as both sides have a serializer and deserializer for it. Standards fall into two categories:
Text based
Human-readable, easy to debug, universally supported. Slower and larger than binary.
| Format | Used For |
|---|---|
| JSON | REST APIs, web — the dominant standard |
| XML | Enterprise, SOAP, legacy systems |
| YAML | Config files (Docker, Kubernetes, GitHub Actions) |
| TOML | Config files (Cargo.toml, pyproject.toml) |
| CSV | Tabular data exports, spreadsheets |
Binary
Not human-readable. Significantly smaller and faster. Used when performance matters.
| Format | Used For |
|---|---|
| Protobuf | gRPC, internal microservices (by Google) |
| Avro | Kafka, data pipelines, data lakes |
| MessagePack | Real-time apps, Redis internals, gaming |
| FlatBuffers | Games, mobile — zero-copy deserialization |
Deserialzation
The reciever get those bits and reconstructs them into its own native type.
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
struct User {
name: String,
age: u32,
scores: Vec<u32>,
active: bool,
}
async fn create_user(Json(user): Json<User>) -> impl IntoResponse {
println!("{}", user.name);
}
Schema/Contract
Both sides need to agree on the shape of the data. This agreement is called a schema or contract
- Implicit schema — both sides just follow a convention. Fragile, breaks silently.
- Explicit schema — tools enforce the shape at the boundary.
- Protobuf → .proto file shared between services
- JSON Schema / OpenAPI → describes and validates JSON structure
- Zod (TypeScript) → runtime validation on received data
Full Round Trip
CLIENT (JavaScript) SERVER (Rust)
─────────────────── ─────────────
JS Object in RAM
{ name: "quantinium", age: 21 }
│
│ JSON.stringify() ← SERIALIZE
▼
"{"name":"quantinium","age":21}"
│
│ HTTP POST (bytes over TCP)
▼
"{"name":"quantinium","age":21}"
│
│ serde_json::from_str() ← DESERIALIZE
▼
User { name: "quantinium", age: 21 }
│
│ (process the request, build response)
│
User { name: "quantinium", verified: true }
│
│ serde_json::to_string() ← SERIALIZE
▼
"{"name":"quantinium","verified":true}"
│
│ HTTP 200 (bytes over TCP)
▼
"{"name":"quantinium","verified":true}"
│
│ JSON.parse() ← DESERIALIZE
▼
JS Object in RAM
{ name: "quantinium", verified: true }