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
omitemptywhere 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.