Introduction
When handling data transfer, it’s easy to forget the step of “serializing and deserializing data” because libraries like Axios abstract it away. Recently while working on backend code I refreshed this knowledge, continuing from my previous post: Go Struct Tag and reflect to explore how Go handles serialized data.
fetch("x", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data)})
// Axios 自動序列化資料axios.post("x", { email: "user@example.com", project_id: "<project_id>"})Serialization and Marshalling
What is Serialization?
Programming languages have their own data structures. For example, Go’s map and JavaScript’s object may look similar but are implemented very differently under the hood. The most common way for them to communicate is via the JSON data format.
Converting an in-memory data structure into a storable or transmittable format is called serialization. Converting it back to the original data structure is called deserialization.
What is Marshalling
The original meaning of “marshal” is to “assemble”, “arrange”, or “organize”.
- Military: “To marshal troops” — assembling scattered soldiers into an orderly formation for action.
- Legal: “To marshal facts” — organizing chaotic information into a logical structure.
- Computer science: Borrowed to mean arranging complex in-memory or pointer-based data structures (e.g., a struct containing pointers) into a flat sequence of bytes suitable for transmission.
Subtle differences between Marshal and Serialization
Although they are often used interchangeably, in computer science terminology there are subtle differences:
| Characteristic | Serialization | Marshalling |
|---|---|---|
| Core intent | Convert data into a persistent format (saving, storing in DB) | Convert data into a transmittable format (inter-process or network communication) |
| Included content | Usually focuses only on the data values themselves | Besides data, may include metadata and even handling for remote procedure calls (RPC) |
| Structural complexity | Emphasizes flattening data | Emphasizes arranging complex, non-contiguous objects into a transmittable form |
Go Marshal
The Go standard library already includes the encoding/json package.
- Struct field names become JSON keys.
- Only fields that start with an uppercase letter can be serialized (exported).
encoding/json struct tag basics
You usually control the output format via struct tags:
import ( "encoding/json" "fmt")
func main() { type User struct { Name string `json:"name"` Email string `json:"email"` }
u := User{ Name: "Riceball", Email: "rice@example.com", }
b, _ := json.Marshal(u) // Convert to JSON: {"name":"Riceball","email":"rice@example.com"} fmt.Println(string(b))}Common tag options
type User struct { ID int `json:"id"` // Rename the field Name string `json:"name,omitempty"` // Do not output if the value is zero Token string `json:"-"` // Ignore this field completely}Unmarshal (Deserialization) example
type User struct { ID int `json:"id"` Name string `json:"name,omitempty"` Email string `json:"email"` Token string `json:"-"`}
var u User// Note: Extra fields in the JSON will be ignored.err := json.Unmarshal([]byte(`{"id":1,"name":"Riceball","email":"rice@example.com","extra":"ignored"}`), &u)if err != nil { fmt.Println("出錯了:", err)}// result: {ID:1 Name:Riceball Email:rice@example.com Token:}fmt.Printf("解析結果: %+v\n", u)JSON Struct Tag advanced usage
string option: force values to strings
When you need to present numbers or booleans as strings in JSON, you can use the string option:
- Useful when interfacing with some legacy systems or APIs that expect all values as strings.
- Prevents precision loss when JavaScript handles very large numbers by sending them as strings.
type Product struct { ID int `json:"id,string"` // output is "123" instead of 123 Price float64 `json:"price,string"` // output is "99.99" instead of 99.99 InStock bool `json:"in_stock,string"` // output is "true" instead of true}
p := Product{ ID: 123, Price: 99.99, InStock: true,}
b, _ := json.Marshal(p)fmt.Println(string(b))// Output: {"id":"123","price":"99.99","in_stock":"true"}Anonymous embedding: flatten nested structures
type Address struct { City string `json:"city"` Country string `json:"country"`}
type User struct { Name string `json:"name"` Address // Anonymous embedding will "elevate" the field to the User level}
u := User{ Name: "Riceball", Address: Address{ City: "Taipei", Country: "Taiwan", },}
b, _ := json.Marshal(u)fmt.Println(string(b))// Output: {"name":"Riceball","city":"Taipei","country":"Taiwan"}// Note: The Address field appears directly at the outermost level, not nested under "address".If you want a nested structure, name it explicitly:
type User struct { Name string `json:"name"` Address Address `json:"address"`}// Output: {"name":"Riceball","address":{"city":"Taipei","country":"Taiwan"}}Mongo Driver
import "go.mongodb.org/mongo-driver/bson"
type Address struct { City string `bson:"city"` Country string `bson:"country"`}
type User struct { Name string `bson:"name"` Address Address `bson:",inline"` // MongoDB's inline syntax}
// File structure after storing in MongoDB:// {// "name": "Riceball",// "city": "Taipei", // Note: Not a nested structure under address// "country": "Taiwan"// }Combining multiple options
type APIResponse struct { Code int `json:"code,string,omitempty"` Message string `json:"message,omitempty"` Data any `json:"data,omitempty"`}Further reading
- What does Go’s JSON package mean when it refers to Marshal? [closed] - stack overflow
- What is the difference between Serialization and Marshaling? - stack overflow
- Go by Example: JSON
- Work with BSON - MongoDB