About
This post documents how Go's encoding/json can be used to parse JSON into a Go struct with the help of the Unmarshal function. We're going to make it a little more useful by using generics as well.
Schema
Payload
{
"ok": true,
"errors": [],
"data": { "pages": 123 } | { "color": "blue" }
}
Response
type Response[T any] struct {
Ok bool `json:"ok"`
Errors []string `json:"errors"`
Data T `json:"data"`
}
Experiment
Overview
Our experiment sets up two examples with slightly different payloads. The first payload includes a book with a page count, and the second payload includes a car with a color. The unmarshal function is used to parse the JSON into a Go struct, and, if successful, the struct is initialized with the data from the JSON.
One word before we begin though, json.Unmarshal expects a
[]byte. If your JSON comes from an io.Reader
(file, network, or stream), use json.Decoder instead, it
can read directly from the stream without buffering the entire input in
memory. Otherwise the information in this post still applies:
Book
package main
import (
"encoding/json"
)
type Response[T any] struct {
Ok bool `json:"ok"`
Errors []string `json:"errors"`
Data T `json:"data"`
}
type Book struct {
Pages int `json:"pages"`
}
func main() {
var res Response[Book]
str := `{"ok": true, "errors": [], "data": {"pages": 123}}`
err := json.Unmarshal([]byte(str), &res)
if err != nil {
panic(err)
}
book := res.Data
println(book.Pages) // 123
}
Car
package main
import (
"encoding/json"
)
type Response[T any] struct {
Ok bool `json:"ok"`
Errors []string `json:"errors"`
Data T `json:"data"`
}
type Car struct {
Color string `json:"color"`
}
func main() {
var res Response[Car]
str := `{"ok": true, "errors": [], "data": {"color": "blue"}}`
err := json.Unmarshal([]byte(str), &res)
if err != nil {
panic(err)
}
car := res.Data
println(car.Color) // blue
}
Explanation
This explanation also applies to the car example, but we'll just talk about the book example for simplicity:
-
type Response[T any] struct { ... }
The Response object provides common fields, and a generic field. -
type Book struct { Pages int }
The book payload fills the role of the generic field (data). -
var book Response[Book]
The Response type, with the Book generic. -
json.Unmarshal(...)
This is where the magic happens.
Our JSON string is turned into a Go struct, or an error is returned. -
book := res.Data
The generic field becomes a concrete type (Book) after unmarshalling. -
println(book.Pages)
This prints the page count.
Conclusion
It is well worth mastering how to parse JSON in Go, and that's the main reason for writing this post, even though the code is pretty straightforward, writing about it is a great way to solidify the knowledge. The use of generics is a nice bonus.