Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
130: encoding/json Marshal and Unmarshal
I once worked with a junior developer who spent an entire afternoon debugging a "silent failure." He had built a beautiful set of structs to represent a user profile, called json.Marshal, and then stared in confusion at the result: an empty JSON object {}. There were no errors, no panics—just an empty object. He was convinced the standard library was broken. The culprit? He had named his struct fields with lowercase letters. In Go, if a field isn't exported (capitalized), the encoding/json package simply can't see it. It's a classic Go rite of passage, and it's exactly why we need to be intentional about how we map our data.
Shipping Data with Marshal
When you want to convert a Go data structure—usually a struct or a map—into a JSON string to send over an API or save to a file, you use json.Marshal. It takes an interface{}, meaning you can pass almost anything into it, and it returns a slice of bytes ([]byte) and an error.
Take a look at this example. I've defined a Book struct. Notice the capitalization; as I mentioned, if these were title or author, the Marshaller would ignore them entirely.
type Book struct {
Title string
Author string
Pages int
}
func main() {
myBook := Book{Title: "The Go Programming Language", Author: "Alan Donovan", Pages: 380}
jsonData, err := json.Marshal(myBook)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(jsonData))
// Output: {"Title":"The Go Programming Language","Author":"Alan Donovan","Pages":380}
}
You'll notice the output keys match the Go field names exactly. While this works, it's rarely what you actually want in a production API, where camelCase or snake_case is the standard.
Mapping Names via Struct Tags
To fix the naming issue without breaking Go's visibility rules, we use "struct tags." These are the small strings you see attached to the end of a field definition. They tell the json package: "I know the field is called Title in Go, but when you turn this into JSON, call it book_title."
type Book struct {
Title string `json:"book_title"`
Author string `json:"author_name"`
Pages int `json:"page_count"`
}
I highly recommend always using tags. It decouples your internal Go naming conventions from your external API contract. If you ever need to rename a field in your code, you can do so without breaking the JSON format your clients expect. You can also use `json:"-"` to tell Go to ignore a field entirely, which is perfect for sensitive data like password hashes.
Hydrating Structs with Unmarshal
Going the other way—taking a JSON string and turning it back into a Go struct—is called unmarshalling. This is where most people trip up because json.Unmarshal requires a pointer to the destination variable. If you pass the variable by value, the function will try to fill a copy of your struct, and the original will remain empty.
Here is how you handle incoming JSON data correctly:
jsonInput := []byte(`{"book_title": "Clean Code", "author_name": "Robert C. Martin", "page_count": 464}`)
var book Book
// We pass &book because Unmarshal needs to modify the variable in place
err := json.Unmarshal(jsonInput, &book)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Loaded %s by %s\n", book.Title, book.Author)
One thing to keep in mind: Unmarshal is forgiving. If the JSON contains fields that aren't in your struct, it just ignores them. If the JSON is missing fields, Go simply leaves the struct fields at their zero value (like "" for strings or 0 for ints). This makes it very resilient, though it means you should handle validation separately if a field is strictly required.
📋 Practical Task
Exercise: Building a Game Server Configuration Parser
You are building a game server that loads its settings from a JSON configuration string. Your task is to create a system that can read this JSON and convert it into a Go struct, while ensuring the internal Go names differ from the JSON keys.
Requirements:
- Define a struct named
ServerConfigwith the following fields:ServerName(JSON key:"server_name")Port(JSON key:"port")MaxPlayers(JSON key:"max_players")AdminEmail(JSON key:"admin_email")
- Create a JSON string containing sample values for these four fields.
- Use
json.Unmarshalto load that JSON string into an instance ofServerConfig. - Print the loaded
ServerNameandPortto the console to verify it worked. - Bonus: Add a field to the struct called
InternalIDand use a struct tag to ensure it is never included if you were toMarshalthe struct back to JSON.
There are no comments for now.