JSON to Go Struct: How to Generate Go Types from API Responses
Go's type system requires you to define structs before you can deserialize JSON data. Unlike dynamically typed languages where you can parse JSON into a generic map or object, Go encourages explicit type definitions with struct tags that control how fields are marshaled and unmarshaled. This guide covers everything you need to know about converting JSON to Go structs, from basic mappings to handling complex nested structures and edge cases.
How Go Handles JSON
Go's standard library provides the encoding/json package for JSON serialization. The two primary functions are json.Marshal() for encoding Go values to JSON and json.Unmarshal() for decoding JSON into Go values. The mapping between JSON fields and struct fields is controlled by struct tags.
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// Unmarshal JSON into a struct
var user User
err := json.Unmarshal([]byte(jsonData), &user)
// Marshal a struct into JSON
data, err := json.Marshal(user)Without struct tags, Go uses the field name directly (case-sensitive). Since JSON conventionally uses camelCase or snake_case while Go uses PascalCase, struct tags are almost always necessary.
Writing Structs Manually vs. Generating Them
For small JSON payloads with a handful of fields, writing structs by hand is straightforward. But when you are working with large API responses containing dozens of fields, deeply nested objects, and arrays, manual struct creation becomes tedious and error-prone. A single typo in a struct tag means that field will silently fail to populate during unmarshaling.
Automated generation tools analyze a sample JSON response and produce the corresponding Go struct definitions with correct field names, types, and tags. This saves time and eliminates common mistakes like mismatched types or misspelled tag names.
Understanding JSON Struct Tags
Struct tags in Go are string literals attached to struct fields that provide metadata for the JSON encoder and decoder. The tag format follows a specific syntax:
type Product struct {
ID int `json:"id"`
ProductName string `json:"product_name"`
Price float64 `json:"price,omitempty"`
InStock bool `json:"in_stock"`
Internal string `json:"-"`
}Key tag options include:
json:"field_name"maps the struct field to a specific JSON key.omitemptyexcludes the field from the JSON output when it holds a zero value (empty string, 0, false, nil).json:"-"completely excludes the field from JSON marshaling and unmarshaling.stringoption marshals the field as a JSON string (useful for numeric IDs that APIs return as strings).
Handling Nested Objects
JSON APIs frequently return nested objects. In Go, these map to nested structs. You can define them as separate named types or as anonymous inline structs:
// Named nested struct
type Address struct {
Street string `json:"street"`
City string `json:"city"`
Country string `json:"country"`
}
type Customer struct {
Name string `json:"name"`
Address Address `json:"address"`
}
// Inline anonymous struct
type Customer struct {
Name string `json:"name"`
Address struct {
Street string `json:"street"`
City string `json:"city"`
Country string `json:"country"`
} `json:"address"`
}Named structs are generally preferred because they can be reused across multiple parent types and are easier to test independently. Anonymous structs work well for one-off nested objects that are only relevant to a single parent.
Working with Arrays and Slices
JSON arrays map to Go slices. When the array contains objects, you use a slice of the corresponding struct type:
type APIResponse struct {
Results []Item `json:"results"`
Total int `json:"total"`
}
type Item struct {
ID int `json:"id"`
Name string `json:"name"`
Tags []string `json:"tags"`
}Handling Null Values and Optional Fields
JSON null values are a common source of bugs in Go. By default, Go's basic types cannot be null. A string defaults to "", an int defaults to 0. To distinguish between "field is missing/null" and "field is present with a zero value," use pointers:
type Profile struct {
Name string `json:"name"`
Bio *string `json:"bio"` // nil when missing
Age *int `json:"age"` // nil when missing
Verified *bool `json:"verified"` // nil when missing
}When a JSON field is null or absent, the pointer will be nil. This pattern is especially important for PATCH endpoints where you need to differentiate between "do not update this field" and "set this field to its zero value."
Handling Timestamps
Go's time.Time type automatically handles ISO 8601 / RFC 3339 formatted date strings during JSON unmarshaling:
type Event struct {
Title string `json:"title"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Works with: "2026-04-12T10:30:00Z"For non-standard date formats or Unix timestamps, you will need to implement the json.Unmarshaler interface on a custom type to handle the parsing manually.
Practical API Example
Here is a real-world example of converting a typical REST API response into Go structs. Consider a user management API that returns:
{
"data": {
"id": 42,
"username": "gopher",
"email": "gopher@example.com",
"profile": {
"display_name": "Go Gopher",
"avatar_url": null,
"created_at": "2026-01-15T08:30:00Z"
},
"roles": ["admin", "editor"]
},
"meta": {
"request_id": "abc-123"
}
}The generated Go structs would look like this:
type APIResponse struct {
Data Data `json:"data"`
Meta Meta `json:"meta"`
}
type Data struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Profile Profile `json:"profile"`
Roles []string `json:"roles"`
}
type Profile struct {
DisplayName string `json:"display_name"`
AvatarURL *string `json:"avatar_url"`
CreatedAt time.Time `json:"created_at"`
}
type Meta struct {
RequestID string `json:"request_id"`
}Try it yourself
Paste any JSON payload and instantly generate Go structs with proper tags, types, and nested struct definitions.
Open JSON to Go Converter →