3. Unmarshal JSON

We can decode JSON bytes (or strings) into a Go struct using json.Unmarshalarrow-up-right or a json.Decoderarrow-up-right.

The Decode method of json.Decoder streams data from an io.Readerarrow-up-right into a Go struct, while json.Unmarshal works with data that's already in []byte format. Using a json.Decoder can be more memory-efficient because it doesn't load all the data into memory at once. json.Unmarshal is ideal for small JSON data you already have in memory. When dealing with HTTP requests and responses, you will likely use json.Decoder since it works directly with an io.Reader.

Example

// res is an http.Response
defer res.Body.Close()

data, err := io.ReadAll(res.Body)
if err != nil {
	return nil, err
}

var issues []Issue
if err := json.Unmarshal(data, &issues); err != nil {
    return nil, err
}

Understanding Unmarshal vs Decoder

json.Decoder (streaming):

decoder := json.NewDecoder(res.Body)
decoder.Decode(&issues)
  • Streams data directly from res.Body

  • More memory efficient for large data

  • Doesn't load everything into memory at once

json.Unmarshal (in-memory):

  • Reads all data into memory first as []byte

  • Then unmarshals the bytes

  • Better for small JSON already in memory

The Solution

Step-by-Step Breakdown

1

Change Return Type

2

Function Name Changed

3

Read Response Body

This converts the stream into a byte slice.

4

Create Issues Slice

Empty slice that will be filled with unmarshaled data.

5

Unmarshal JSON

  • Takes []byte of JSON

  • Decodes into &issues (address so it can modify)

6

Return Issues

Return the decoded slice and no error.

Complete Working Code

The Flow

HTTP Response ↓ res.Body (stream of bytes) ↓ io.ReadAll() β†’ data []byte (all bytes in memory) ↓ json.Unmarshal(data, &issues) β†’ []Issue (Go structs) ↓ return issues

Comparison: Decoder vs Unmarshal

Using Decoder (from previous lesson)

Using Unmarshal (this lesson)

Both accomplish the same thing!

When to Use Each

Use Decoder when:

  • Working with streams (HTTP responses, files)

  • Want memory efficiency

  • Processing large JSON

Use Unmarshal when:

  • JSON is already in []byte format

  • Working with small JSON

  • Need to do something with raw bytes first

Key Takeaways

circle-info
  • io.ReadAll reads entire response into []byte

  • json.Unmarshal decodes []byte into structs

  • &issues passes address so Unmarshal can modify it

  • Return []Issue instead of []byte to give decoded data

Assignment

Update the getIssueData function in http.go.

1

Change the return signature to return []Issue instead of []byte.

2

Because the function will now return a decoded slice of issues, change the name from getIssueData to getIssues.

3

Get the data from the response body using io.ReadAll, creating a slice of bytes []byte.

4

Create a nil slice of issues []Issue.

5

Use json.Unmarshal on the data to get the JSON data.

6

Return the issues.

Solution

(Note: the solution illustrates the required changes β€” updating the return type/name and using io.ReadAll + json.Unmarshal to return a decoded []Issue.)