JSON is a widely used notation for sending and receiving structured information. Go supports JSON encoding and decoding via encoding/json standard library package.

Let's say we want to encode a list of books as JSON. First we define a custom type based on struct to represent a book:

// books/main.go
type Book struct {
    Title   string
    Authors []string
    Year    int `json:"published,omitzero"`
}

Encoding

The struct fields that you want to encode as JSON must be exported (capitalized). The string after the Year field is called a field tag. The published piece of the field tag specifies the name of the field in JSON instead of the original Year. The omitzero option basically omits the field in JSON if it has the zero value for its type.

Now we create some Book data and encode (marshal) them as indented JSON:

// books/main.go
books := []Book{
    {"Lord of the Rings", []string{"J.R.R. Tolkien"}, 0},
    {"That Hideous Strength", []string{"C.S. Lewis"}, 1945},
    {"The Go Programming Language", []string{"Alan A. A. Donovan", "Brian W. Kernighan"}, 2015},
}
data, err := json.MarshalIndent(books, "", "    ")
if err != nil {
    log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Println(string(data))

Decoding

To decode JSON to Go data we use the Unmarshal function:

// books/main.go
var titles []struct{ Title string }
if err := json.Unmarshal(data, &titles); err != nil {
    log.Fatalf("JSON unmarshaling failed: %s", err)
}
for _, t := range titles {
    fmt.Println(t.Title)
}

Note that we chose to decode only the book titles.

Practical example

Now let's build a practical tool. GitHub exposes lot of JSON data via HTTP endpoints. For example to get information about repositories of a GitHub organization named golang you visit https://api.github.com/orgs/golang/repos.

We are interested in the following pieces of information about repositories:

// ghrepos/main.go
type Repo struct {
	Name        string   `json:"name"`
	CloneUrl    string   `json:"clone_url"`
	Stars       int      `json:"stargazers_count"`
	Topics      []string `json:"topics"`
	Description string   `json:"description"`
}

Next we get the JSON data and decode them to a slice of Repos:

// ghrepos/main.go
func getRepos(url string) ([]Repo, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

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

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

All that's needed now is to somehow print the data, for example:

// ghrepos/main.go
func printRepos(repos []Repo) {
	tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
	const format = "%v\t%v\t%v\t%v\n"
	fmt.Fprintf(tw, format, "Name", "Description", "Stars", "URL")
	fmt.Fprintf(tw, format, "----", "-----------", "-----", "---")
	for _, k := range repos {
		fmt.Fprintf(tw, format,
			truncate(k.Name, 15),
			truncate(k.Description, 30),
			k.Stars,
			k.CloneUrl,
		)
	}
	tw.Flush()
}

This produces output like:

$ go run ./ghrepos/main.go -org golang
Name        Description                     Stars   URL
----        -----------                     -----   ---
gddo        Go Doc Dot Org                  1103    https://github.com/golang/gddo.git
lint        [mirror] This is a linter f...  3968    https://github.com/golang/lint.git
glog        Leveled execution logs for Go   3612    https://github.com/golang/glog.git
<...>

Reply

or to participate