← back to the desk

Serialization and the format in the middle

3 September 2026 · 4 min read · #backend #json #http

written from this video


A React app sends this:

{ name: "Mudit", age: 24 }

A Rust server receives it and does this with it:

struct User { name: String, age: u32 }

Those two things are not the same object. They aren't even the same kind of thing. JavaScript is dynamically typed and interpreted; every number is a float and every object is a hash map that can grow a field at runtime. Rust is compiled and strict; age is a u32 occupying exactly four bytes, and a struct's shape is decided before the program ever runs. There is no memory layout the two languages share, so nothing can be handed across directly.

Serialization is what closes that gap. The client converts its in-memory value into an agreed-upon format, that format travels over the wire, and the server converts the format into its own in-memory value. Deserialization is the same trip in the other direction. That's the whole concept.

Why a common format is the only answer

Try designing this yourself. Two machines, different languages, connected over a network. The client needs to send structured data; the server needs to understand it. Your options are:

  1. Teach the server to read JavaScript objects, and every other language it might talk to.
  2. Pick a format both sides agree on, and make each side responsible for its own translation.

Option 1 is N×N adapters and it collapses the second a third language shows up. Option 2 is N translators, one per language, and each side only has to know the standard plus itself. So: define a format, publish the rules, and every participant writes to_format and from_format once.

That is what a serialization standard is. Not a clever mechanism — a set of rules everyone agreed to follow.

The layer you're responsible for

If you've seen the OSI model you know the application layer is only the top of the stack. Below it, your JSON gets chopped into segments, wrapped in IP packets, framed, and eventually pushed onto a wire as voltage or light. On the far side that all runs backwards.

You do not care. As a backend engineer the useful mental model is:

client:  object  ->  JSON  ->  [network does things]
server:  [network does things]  ->  JSON  ->  struct

The intermediate representations are somebody else's abstraction, and they're guaranteed to reconstruct the bytes you handed down. What you own is exactly one conversion: your language's types to the standard, and back.

Worth knowing the OSI model exists and roughly what the layers do. Not worth memorising frame formats to write an API.

The standards, briefly

Serialization formats split into two families.

Text-based: JSON, YAML, XML. Human-readable — you can open the payload in a text editor and see what went wrong. That readability costs bytes: every key name is transmitted in full, on every single object, in every response.

Binary: Protocol Buffers, Avro, MessagePack. Compact and fast, because the schema is known ahead of time and the field names don't travel with the data. Also completely opaque without the schema to decode it, which makes debugging a different exercise.

For HTTP APIs, JSON wins by a landslide — call it 80% of traffic and you won't be far off. Protobuf shows up when you move to gRPC or when payload size genuinely matters. YAML lives in config files. XML lives in enterprise systems and SOAP endpoints you inherited.

The rest of this is about JSON, because that's what you'll actually type.

JSON's rules fit in a paragraph

JSON stands for JavaScript Object Notation, which explains why it looks like a JS object literal, and misleads people into thinking it's a JavaScript thing. It isn't. Every language ships a JSON parser.

{
  "id": 1,
  "title": "Some book",
  "inStock": true,
  "tags": ["backend", "json"],
  "address": {
    "country": "India",
    "pincode": 560001
  }
}

The rules:

  • An object opens with { and closes with }.
  • Keys are always strings, always in double quotes. Not single quotes. Not bare. There is no other key type.
  • Values are a string, a number, a boolean, null, an array, or another object.
  • A nested object follows the same rules, recursively.

That's the entire grammar. The gotchas that follow from it are worth internalising: no trailing commas, no comments, and no dates. A JSON date is a string like "2026-09-09T10:00:00Z" by convention, and it's your parser that turns it into a real timestamp on both ends.

What it looks like in a real request

Hit a POST /api/books and inspect the traffic. The request body:

{ "id": 4, "title": "Dune", "author": "Frank Herbert" }

The server deserializes those three fields into whatever its language calls a record, runs the business logic, and serializes the result back:

{
  "data": [
    { "id": 1, "title": "Dune", "author": "Frank Herbert" }
  ]
}

The client parses that string back into an array of objects and renders it. Neither side inspected the other's types. Both sides only had to agree that the bytes between them are JSON.

The word for the whole round trip is serialization and deserialization, and it's less a technique you implement than a phenomenon you should be able to name. Your framework already does it: Express calls JSON.parse on the body, Axum runs serde over it, FastAPI hands it to Pydantic. You'll only think about it directly when it breaks — a Content-Type that lies, a float that lost precision, a key that arrived as user_id when your struct expected userId. Every one of those bugs is a disagreement about the standard, which is the one thing both sides were supposed to have agreed on.