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
53: JSON Encoding and Decoding for APIs
I want to build a small service that handles a book inventory. In a real-world API, we aren't just moving data around internally; we're talking to front-ends or other services. That means we need to speak JSON. Let's start with the most basic thing: taking a Go struct and turning it into a JSON string.
The Capitalization Conflict
I'll start by defining a Book struct. Since I want the json package to be able to see these fields, I have to export them by capitalizing the first letter. If I don't, the encoder will just ignore them entirely.
package main
import (
"encoding/json"
"fmt"
)
type Book struct {
Title string
Author string
Pages int
}
func main() {
myBook := Book{Title: "The Go Programming Language", Author: "Alan Donovan", Pages: 380}
data, _ := json.Marshal(myBook)
fmt.Println(string(data))
}
When I run this, I get: {"Title":"The Go Programming Language","Author":"Alan Donovan","Pages":380}. Now, here is the problem. In the Go world, we capitalize for visibility. But in the JSON/JavaScript world, Title (uppercase) is unconventional. Most APIs expect camelCase, like title. I can't just lowercase the struct fields in Go, or the json package won't be able to access them. It's a bit of a catch-22.
Solving the Naming Game
To fix this, I need to tell Go exactly how I want these fields to look when they leave the program. We do this with "struct tags." These are those little strings wrapped in backticks that you've probably seen in a lot of Go code.
type Book struct {
Title string `json:"title"`
Author string `json:"author"`
Pages int `json:"pages"`
}
Now, when I run json.Marshal again, the output is {"title":"The Go Programming Language","author":"Alan Donovan","pages":380}. Perfect. I've decoupled my internal Go naming conventions from my external API contract. I should also mention that if I have a field I want to keep internal—say, a DatabaseID—I just leave it lowercase (databaseID) and the JSON package will ignore it completely.
Decoding the Incoming Request
Encoding is the easy part. The real work happens when a client sends JSON to us. I'll try the most straightforward approach: reading the whole body into a byte slice and then unmarshaling it.
// Imagine this is inside an HTTP handler
var b Book
err := json.Unmarshal(bodyBytes, &b)
if err != nil {
fmt.Println("Error decoding JSON:", err)
}
This works fine for tiny payloads. But if I'm building a production API, reading the entire request body into memory just to parse it is wasteful. The http.Request.Body is an io.Reader, and the json package gives us a better way to handle streams: json.NewDecoder.
Streaming with NewDecoder
Instead of Unmarshal, I'll use a decoder. This reads directly from the network socket, which is much more memory-efficient for larger requests.
func handleBookUpload(w http.ResponseWriter, r *http.Request) {
var b Book
// We wrap the request body in a decoder and decode directly into the struct
err := json.NewDecoder(r.Body).Decode(&b)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
fmt.Fprintf(w, "Received book: %s", b.Title)
}
One thing I've noticed when doing this is that if the JSON is missing a field, Go doesn't complain; it just leaves that field as the "zero value" (like an empty string or 0). If you need to distinguish between a field being "missing" and a field being "explicitly set to empty," you'll need to use pointers in your struct (e.g., *string), but for 90% of APIs, the standard zero-value behavior is exactly what you want.
📋 Practical Task
Exercise: Building a Movie Review Parser
You are tasked with creating a tool that processes movie review data sent by a client. The client sends a JSON object representing a movie review, but their naming convention is slightly different from your internal Go naming.
Requirements:
- Create a struct named
MovieReviewwith the following fields:MovieTitle(mapped to JSON key"movie_title")Rating(mapped to JSON key"star_rating")ReviewerName(mapped to JSON key"reviewer")InternalID(this field should NOT appear in the JSON at all)
- Write a function that takes a JSON string (as a
[]byte) and decodes it into yourMovieReviewstruct. - Write a second function that takes a
MovieReviewstruct and encodes it back into a JSON string, verifying that the keys match the requirements. - Test your code with this JSON input:
{"movie_title": "Inception", "star_rating": 5, "reviewer": "Jane Doe"}. Ensure theInternalIDremains hidden.
There are no comments for now.