Skip to content

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.
  • omitempty excludes 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.
  • string option 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 →