How to Convert Go Struct to JSON: From Beginner to Pro
Working with JSON is the daily bread of any backend developer in Go. Almost every day you need to send data to a client, accept requests from an API, or save configurations. And in 90% of cases, it's JSON. But how do you properly convert your native struct into readable JSON? It seems simple, right? However, the devil is in the details: incorrect tags, empty fields, forgotten errors — and suddenly your API returns something unexpected.
In this article, we will cover converting Go struct to JSON from the basics to advanced techniques. You will learn how tags work, how to hide empty fields, how to serialize nested structures, and how to avoid errors. Let's go!
Basics: The encoding/json Package
Go's standard library includes a powerful package called encoding/json. Its main tools are the functions json.Marshal (to convert a struct to JSON) and json.Unmarshal (for the reverse process).
Let's look at a basic example. We have a user structure:
package main
import ( "encoding/json" "fmt")
type User struct { Name string Age int Email string}
func main() { user := User{ Name: "Alexey", Age: 30, Email: "alex@example.com", }
jsonData, err := json.Marshal(user) if err != nil { fmt.Println("Error:", err) return }
fmt.Println(string(jsonData)) // Output: {"Name":"Alexey","Age":30,"Email":"alex@example.com"}}It works, but there's a nuance: the JSON keys match the struct field names (capitalized). This doesn't follow JSON conventions (usually camelCase or snake_case is used).
JSON Tags: Controlling Key Names
To control how fields appear in JSON, you use struct tags. These are special annotations written after the field type in backticks.
type User struct { Name string `json:"name"` Age int `json:"age"` Email string `json:"email"`}
// Now the output will be:// {"name":"Alexey","age":30,"email":"alex@example.com"}Tags solve several tasks:
- Changing the field name — makes keys clean and readable
- Skipping a field — using
json:"-"completely excludes the field from JSON - Managing empty fields — the
omitemptyoption hides the field if it contains a zero value
Practical Example with omitempty
type User struct { Name string `json:"name"` Age int `json:"age,omitempty"` Email string `json:"email,omitempty"` IsAdmin bool `json:"is_admin,omitempty"`}
func main() { user := User{Name: "Maria"} jsonData, _ := json.Marshal(user) fmt.Println(string(jsonData)) // Output: {"name":"Maria"} // Age, Email, and IsAdmin are omitted because they are empty}Note: omitempty works for zero values — 0, false, empty string, nil. This is very convenient for APIs where you don't need to carry extra fields.
Nested Structures and Slices
In real projects, data is rarely flat. Often you have nested objects or arrays. Go handles this perfectly — the json package recursively processes all fields.
type Address struct { City string `json:"city"` Street string `json:"street"` ZipCode string `json:"zip_code"`}
type Company struct { Name string `json:"name"` Address Address `json:"address"` Tags []string `json:"tags"`}
func main() { company := Company{