Serialization, the shared language of machines.
A JavaScript client and a Rust server have nothing in common at the level of data types. Serialization is how they talk anyway: both convert their native data to and from one agreed format for the trip across the network. This manual covers the whole idea — the language barrier, the common standard, text vs binary formats, JSON in depth, the OSI mental model, and the full client⇄server round-trip — with worked code in Go and Python.
The Language Barrier
A typical web application is two machines in different places, connected over the internet. The client — the frontend, often a JavaScript app (React, Angular, Vue) running in a browser like Chrome — talks to a server, the backend, which may run on localhost or somewhere remote in AWS, GCP, or Azure. They communicate through some network protocol: HTTP (traditional REST APIs), or gRPC, or WebSocket. This manual assumes HTTP/REST, since it is still the most common.
Here is the catch. The client might be JavaScript and the server might be Rust — and those two languages handle data completely differently:
- JavaScript is dynamic and not compiled — types are loose and decided at runtime.
- Rust is compiled and extremely strict about types.
So when a JavaScript client sends an object like { name: "..." } in the request body, the Rust server cannot natively understand a JavaScript data structure — its own data types are nothing like JavaScript’s. The same problem runs in reverse for the response. Two machines, two incompatible internal worlds, one wire between them.
Serialize & Deserialize
The solution: both sides agree on a single, common, standard format for data on the wire. Each machine converts into that format before sending, and out of it after receiving.
Converting native data (a JavaScript object, a Rust struct, a Go struct, a Python object) into the common standard format before sending it over the network (or storing it).
The exact reverse: taking data in the common format and parsing it back into the machine’s own native data type, so it can run its business logic.
Because the format is neutral, any machine can talk to any other machine regardless of the languages or technologies underneath. “Language-agnostic” = it doesn’t care whether you’re JS or Rust. “Domain-agnostic” = it doesn’t care which environment or technology stack you run.
The same convert-to-and-from-a-common-format idea also applies to storage — writing data to disk or a config/log file and reading it back later. Transmission is the focus here, but the phenomenon is the same.
Agreeing on a Common Standard
If you were handed this problem cold — two machines in different locations, connected over the internet, that must exchange data in a way that is language-agnostic — the obvious solution is to define a common standard.
Just a fancy word for an agreed set of rules describing how data must be written. Both client and server promise: “we will send and receive data shaped like this.”
With that agreement in place, the client converts its native logic (say, from JavaScript) into the standard before sending; the server reads the standard and converts it into its native type (say, a Rust struct); it does its work, converts the response back into the standard, and sends it; and the client parses it back into JavaScript. That round-trip is serialization and deserialization. In one line: converting data to and from a common format during transmission (or storage), so it is understandable across languages and domains.
Text-based vs Binary Formats
There are many serialization standards, but they fall into two families.
- Text-based formats — human-readable.
JSON,YAML, andXML. You can open the payload and read it. - Binary formats — compiled into raw binary for highly efficient, compact transmission.
Protobuf(Protocol Buffers) andAvroare the popular ones. Not meant to be read by eye.
YAML and XML are still used for serialization, but not so often for HTTP communication. For traditional HTTP REST APIs, the default choice is JSON.
JSON — the Industry Standard
For traditional HTTP REST API communication, JSON is the most popular serialization standard — used something like 80% of the time.
JavaScript Object Notation. Named because it looks and behaves much like a JavaScript object — but despite the name it is not limited to JavaScript and is used everywhere, across every language.
Why it won
- Human-readable. JSON was designed to be read by people; you can glance at a payload and understand it. That is one of its strongest selling points.
- Fundamental data types. Its value types map cleanly onto concepts every programmer already knows.
Where you’ll meet it
- HTTP transmission — the request/response bodies of REST APIs (the focus here).
- Logging — a very popular choice for application and server logs at runtime.
- Configuration files — app and tool config.
JSON Syntax Rules
JSON has very few rules — that is the whole point. Here is a typical object, annotated with every rule the source calls out:
The rules, spelled out
- A JSON object starts with
{and ends with}. - Keys must be strings wrapped in double quotes — e.g.
"name":. No other data type can be a key. (Double quotes, always — not single.) - Values are limited to fundamental types: a string, a number, a boolean, an array, or another nested object.
- A nested object follows the exact same rules recursively: braces, quoted string keys, and values of those same fundamental types. JSON can nest as deep as you like.
There isn't much more to memorise. Braces around the object, double-quoted string keys, and values that are strings, numbers, booleans, arrays, or nested objects — each nested object obeying the same characteristics. That is all there is to JSON.
The OSI Mental Model
When data crosses the internet it passes through many network layers — the OSI model. You do not need to master IP packets and data frames to be a backend engineer; a high-level picture is enough. The two ends of the stack are the application layer (top) and the physical layer (bottom); there are several layers in between, and the same stack exists on both client and server.
You only focus on the application layer. Assume your client serializes data into JSON and sends it. Under the hood the network turns that JSON into data frames → IP packets → physical bits (0s and 1s) sent as electrical or optical signals. Before your server ever touches the data, the network reassembles those bits back into the exact same JSON the client sent. You never deal with the intermediate conversions — only the JSON.
The End-to-End Workflow
Putting every piece together, here is the complete round-trip in a real request — the phenomenon that, as a whole, is serialization & deserialization.
In words: the client gathers input, serializes it to a JSON string in the request body, and ships it; the bits cross the internet; the server deserializes the JSON into its native type, runs business logic (e.g. saves to a database), serializes its response back to JSON, and sends it; the client deserializes that JSON into a JavaScript object and updates the UI. That whole flow is what we call serialization and deserialization — a phenomenon you mostly just need to know exists; it has very few concepts of its own.
Serialization in Go
Go serializes to/from JSON with the standard encoding/json package: Marshal = serialize, Unmarshal = deserialize. The pillars show up through interfaces (abstraction + polymorphism), unexported fields + struct tags (encapsulation), and embedding (composition — Go’s inheritance). Pillars are tagged OOP.
package main import ("encoding/json"; "fmt"; "time") // ABSTRACTION: Serializer names a capability — "turn data into // bytes and back" — without binding to a concrete format. Code // depends on this contract, so JSON could be swapped for another // format with zero changes to callers. type Serializer interface { Serialize(v any) ([]byte, error) // native -> common format Deserialize(data []byte, v any) error // common format -> native } // POLYMORPHISM: JSONSerializer implements Serializer. Any other // format (YAML, Protobuf) could implement the same interface and // be used interchangeably through a Serializer variable. type JSONSerializer struct{} func (JSONSerializer) Serialize(v any) ([]byte, error) { return json.Marshal(v) // SERIALIZE: struct -> JSON bytes } func (JSONSerializer) Deserialize(data []byte, v any) error { return json.Unmarshal(data, v) // DESERIALIZE: JSON bytes -> struct } // "INHERITANCE" via COMPOSITION: BaseModel holds shared fields; // embedding it into User reuses them (and their json tags). type BaseModel struct { ID int `json:"id"` // tag = the JSON key name CreatedAt time.Time `json:"created_at"` } // ENCAPSULATION: an exported struct whose JSON shape is controlled // by tags. "password" is unexported (lowercase) -> private AND // invisible to the JSON encoder, so it never leaks over the wire. type User struct { BaseModel // embedded -> inherits ID, CreatedAt Name string `json:"name"` Active bool `json:"active"` Address Address `json:"address"` // nested object password string // unexported: hidden from JSON output } type Address struct { Country string `json:"country"` Phone int `json:"phone"` } func main() { var codec Serializer = JSONSerializer{} // program to the interface u := User{ BaseModel: BaseModel{ID: 1, CreatedAt: time.Now()}, Name: "Ada", Active: true, Address: Address{Country: "India", Phone: 123456}, } // SERIALIZE — native Go struct into the common JSON format out, _ := codec.Serialize(u) fmt.Println(string(out)) // {"id":1,"created_at":"...","name":"Ada","active":true, // "address":{"country":"India","phone":123456}} // DESERIALIZE — JSON received over HTTP back into a Go struct incoming := []byte(`{"name":"Lin","address":{"country":"IN","phone":42}}`) var back User codec.Deserialize(incoming, &back) fmt.Println(back.Name, back.Address.Country) // Lin IN }
Serialization in Python
Python uses the built-in json module: json.dumps = serialize (dump to string), json.loads = deserialize (load from string). The same OOP architecture: an ABC contract, inheritance, polymorphism, and a private attribute.
import json from abc import ABC, abstractmethod from dataclasses import dataclass, asdict, field # ABSTRACTION: Serializer is an Abstract Base Class. The # @abstractmethod forces subclasses to implement both directions. # You cannot instantiate Serializer itself — it is a pure contract. class Serializer(ABC): @abstractmethod def serialize(self, obj) -> str: ... # native -> common @abstractmethod def deserialize(self, data: str): ... # common -> native # INHERITANCE + POLYMORPHISM: JSONSerializer IS-A Serializer and # overrides both methods. A YamlSerializer could subclass the same # ABC and be dropped in wherever a Serializer is expected. class JSONSerializer(Serializer): def serialize(self, obj) -> str: return json.dumps(obj) # SERIALIZE: dict -> JSON text def deserialize(self, data: str): return json.loads(data) # DESERIALIZE: JSON text -> dict # INHERITANCE: BaseModel is a shared parent (id, timestamps). @dataclass class BaseModel: id: int = 0 @dataclass class Address: country: str phone: int # nested object # ENCAPSULATION: __password is name-mangled (-> _User__password), # effectively private, and to_dict() chooses what leaves the # object — the secret never appears in the serialized output. @dataclass class User(BaseModel): # IS-A BaseModel name: str = "" active: bool = True address: Address | None = None __password: str = "" # private; excluded below def to_dict(self) -> dict: d = asdict(self) d.pop("_User__password", None) # keep the secret out return d if __name__ == "__main__": codec: Serializer = JSONSerializer() # program to the contract user = User(id=1, name="Ada", address=Address("India", 123456)) # SERIALIZE — native object into the common JSON format payload = codec.serialize(user.to_dict()) print(payload) # {"id": 1, "name": "Ada", "active": true, # "address": {"country": "India", "phone": 123456}} # DESERIALIZE — JSON received over HTTP back into native data incoming = '{"name": "Lin", "address": {"country": "IN", "phone": 42}}' data = codec.deserialize(incoming) print(data["name"], data["address"]["country"]) # Lin IN
Both define a Serializer contract (abstraction), implement it for JSON (polymorphism), share fields through a base model (inheritance / composition), and keep secrets out of the wire format (encapsulation). The verbs are universal: serialize = native → JSON (Go Marshal, Python dumps); deserialize = JSON → native (Go Unmarshal, Python loads).
Glossary
Every term from the source, in one place.
| Term | Meaning |
|---|---|
| Serialization | Converting native data (object/struct) into a common standard format to send or store. |
| Deserialization | The reverse — parsing the common format back into the machine's native data type. |
| Client / frontend | The app sending requests, often a JavaScript app in a browser. |
| Server / backend | The machine receiving requests and running business logic (e.g. a Rust app). |
| Native data type | A language's own internal representation — a JS object, a Rust/Go struct, a Python object. |
| Parse | To read a format and turn it into something the machine can use. |
| Common standard / format | An agreed set of rules for how data is written, so both sides understand it. |
| Language-agnostic | Works regardless of which programming language each side uses. |
| Domain-agnostic | Works regardless of the environment / technology stack on each side. |
| Text-based format | Human-readable serialization — JSON, YAML, XML. |
| Binary format | Compact, efficient, non-readable serialization — Protobuf, Avro. |
| JSON | JavaScript Object Notation; the ~80% choice for HTTP REST; human-readable; not JS-only. |
| Key / value | JSON keys are quoted strings; values are string, number, boolean, array, or nested object. |
| Nested object | A JSON object inside another, following the same rules recursively. |
| OSI model | The layered model of network communication, from application layer down to physical layer. |
| Application layer | The top layer — where you, the backend engineer, work in JSON. |
| Physical layer | The bottom layer — raw bits (0s and 1s) as electrical/optical signals. |
| Data frames / IP packets | Intermediate forms the data takes between application and physical layers. |
Two machines with incompatible native data types agree on a common format (usually JSON). Each serializes its data into that format before sending and deserializes it back after receiving — so communication is language- and domain-agnostic. The network turns JSON into bits and back; at the application layer you only ever deal with the JSON.