A Detailed Backend Reference

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.

native data common format language-agnostic JSON · ~80% 11 sections
Part I — The ProblemWhy this exists
01

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.

CLIENT · JavaScript dynamic · not compiled { name : "Ada" } SERVER · Rust compiled · strict types struct User { name : String } ? incompatible data types
The barrier. A Rust server can't natively parse a JavaScript object. They need a universal language for the trip across the network.
02

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.

Serialization

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).

Deserialization

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.

native data object · struct common format e.g. JSON text native data struct · object serialize → → deserialize
Two directions, one format. Serialize = native → common. Deserialize = common → native. The format in the middle is what travels.
Language-agnostic & domain-agnostic

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.

Not only for the network

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.

Part II — The StandardsWhich format to agree on
03

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.

A “standard” / “format”

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.

04

Text-based vs Binary Formats

There are many serialization standards, but they fall into two families.

Serialization standards TEXT-BASED human-readable JSON YAML XML BINARY FORMAT compact · efficient · not human-readable Protobuf Avro
Two families. Text-based formats are readable by humans; binary formats compile to raw bytes for compact, efficient transmission.
  • Text-based formats — human-readable. JSON, YAML, and XML. You can open the payload and read it.
  • Binary formats — compiled into raw binary for highly efficient, compact transmission. Protobuf (Protocol Buffers) and Avro are 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.

05

JSON — the Industry Standard

For traditional HTTP REST API communication, JSON is the most popular serialization standard — used something like 80% of the time.

JSON

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.
06

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:

{ "name" : "Ada" , "age" : 30 , "active" : true , "tags" : [ "a" , "b" ], "address" : { "country" : "India" , "phone" : 123456 } opening brace { key — string, in double quotes values: string, number, boolean, array … … or a NESTED object (same rules) closing brace }
Every rule in one object. Braces wrap it; keys are quoted strings; values are simple types or nested objects.

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.
That's the whole spec, basically

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.

Part III — Under the HoodWhat to ignore, and what not to
07

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.

CLIENT Application — JSON you live here …transport / network… data frames · IP packets Physical — bits 0 1 0 1 0 1 SERVER Application — JSON you live here too …transport / network… data frames · IP packets Physical — bits 0 1 0 1 0 1 the wire — electrical / optical signals carrying 0s and 1s JSON → bits bits → JSON
The abstraction. Your data starts and ends as JSON at the application layer. In between it becomes frames, packets, and bits — but that is not your concern.
The backend engineer’s mental model

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.

08

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.

CLIENT (JavaScript) 1 · gather user input 2 · SERIALIZE → JSON attach to HTTP body 3 · TRANSMIT JSON → bits → across the internet SERVER (Rust) 4 · DESERIALIZE JSON → Rust struct 5 · PROCESS · save to DB 6 · SERIALIZE → JSON the HTTP response transmit back JSON → bits → across the internet 7 · DESERIALIZE JSON → JS object update the UI
The full round-trip. Serialize on each side before sending; deserialize on each side after receiving. Four conversions per request/response cycle.

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.

Part IV — ImplementationSerialize for real, with the OOP showing
09

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.

Go 1.22+serialize.go
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
}
10

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.

Python 3.11+serialize.py
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
Same shape, two languages

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).

11

Glossary

Every term from the source, in one place.

TermMeaning
SerializationConverting native data (object/struct) into a common standard format to send or store.
DeserializationThe reverse — parsing the common format back into the machine's native data type.
Client / frontendThe app sending requests, often a JavaScript app in a browser.
Server / backendThe machine receiving requests and running business logic (e.g. a Rust app).
Native data typeA language's own internal representation — a JS object, a Rust/Go struct, a Python object.
ParseTo read a format and turn it into something the machine can use.
Common standard / formatAn agreed set of rules for how data is written, so both sides understand it.
Language-agnosticWorks regardless of which programming language each side uses.
Domain-agnosticWorks regardless of the environment / technology stack on each side.
Text-based formatHuman-readable serialization — JSON, YAML, XML.
Binary formatCompact, efficient, non-readable serialization — Protobuf, Avro.
JSONJavaScript Object Notation; the ~80% choice for HTTP REST; human-readable; not JS-only.
Key / valueJSON keys are quoted strings; values are string, number, boolean, array, or nested object.
Nested objectA JSON object inside another, following the same rules recursively.
OSI modelThe layered model of network communication, from application layer down to physical layer.
Application layerThe top layer — where you, the backend engineer, work in JSON.
Physical layerThe bottom layer — raw bits (0s and 1s) as electrical/optical signals.
Data frames / IP packetsIntermediate forms the data takes between application and physical layers.
The whole idea in one breath

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.