Working With JSON in Go (encoding/json)

Go has native JSON support in the encoding/json package, no external dependency needed. Go’s approach is a bit different from languages like Python or JavaScript: structs (with specific tags) play the role that annotated classes play in other languages, and error handling is explicit, following Go’s own conventions.

Importing the package

import "encoding/json"

Converting JSON into a struct (unmarshal)

First, define a struct with json tags indicating how each field relates to the JSON keys.

// golang
type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

Then use json.Unmarshal().

// golang
package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    jsonData := []byte(`{"name": "Ana", "age": 30}`)

    var person Person
    err := json.Unmarshal(jsonData, &person)
    if err != nil {
        fmt.Println("Error parsing JSON:", err)
        return
    }

    fmt.Println(person.Name) // Ana
}

Notice three things typical of Go here: json.Unmarshal() takes a []byte (not a string directly), takes a pointer (&person) so it can populate the struct, and the error is returned explicitly, with no exceptions.

Why json tags matter

Without the json:"name" tag, Go would try to match the JSON key against the exact struct field name (Name, capitalized). Since Go’s convention requires exported fields (accessible outside the package) to start with a capital letter, but JSON keys usually come in lowercase or camelCase, the tag resolves that mismatch.

// golang
type Person struct {
    Name string `json:"name"` // without the tag, it would only match the key "Name"
}

Converting a struct into JSON (marshal)

// golang
person := Person{Name: "Ana", Age: 30}

jsonData, err := json.Marshal(person)
if err != nil {
    fmt.Println("Error generating JSON:", err)
    return
}

fmt.Println(string(jsonData)) // {"name":"Ana","age":30}

json.Marshal() returns a []byte, which is usually converted to a string when displaying or sending it.

Formatting the output JSON (pretty print)

// golang
jsonData, err := json.MarshalIndent(person, "", "  ")

json.MarshalIndent() takes two extra parameters: a prefix (usually empty) and the indentation string (here, two spaces).

Omitting empty fields

By default, Go includes every struct field in the generated JSON, even ones with a zero value (empty string, 0, false). To omit empty fields, use omitempty.

// golang
type Person struct {
    Name     string `json:"name"`
    Age      int    `json:"age,omitempty"`
    Nickname string `json:"nickname,omitempty"`
}

With omitempty, if Age is 0 or Nickname is an empty string, those keys simply won’t appear in the generated JSON.

Ignoring a field entirely

If you have a field in the struct that should never show up in the JSON (for example, a password), use -.

// golang
type User struct {
    Name     string `json:"name"`
    Password string `json:"-"`
}

Working with JSON of unknown structure

If you don’t know the exact JSON structure ahead of time, you can use map[string]interface{} (or any in more recent Go versions).

// golang
var data map[string]interface{}
json.Unmarshal(jsonData, &data)

fmt.Println(data["name"]) // access it like a dynamic map

This is more flexible, but it loses the compile-time type checking a struct gives you, so it’s worth using sparingly, only when it’s really needed.

Quick checklist

  • Did you define the json:"..." tags correctly for each struct field?
  • Are you explicitly handling the error returned by Unmarshal/Marshal?
  • Using omitempty where it makes sense to omit empty fields from the generated JSON?
  • Using - for fields that should never be serialized (like passwords)?

To quickly validate a JSON before mapping it to a Go struct, paste it into JSON Validator.