Skip to Content
Course content

130: encoding/json Marshal and Unmarshal

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 ServerConfig with 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.Unmarshal to load that JSON string into an instance of ServerConfig.
  • Print the loaded ServerName and Port to the console to verify it worked.
  • Bonus: Add a field to the struct called InternalID and use a struct tag to ensure it is never included if you were to Marshal the struct back to JSON.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.