JSON to Go Struct

Turn a JSON response into Go structs with the json tags already written. Field names follow Go's naming conventions, so you get ID and URL rather than Id and Url.

JSON Input
Generated Code

1 JSON to Go Struct example

JSON sample

1
2
3
4
5
6
7
{
  "id": 1,
  "name": "Ana",
  "orders": [
    { "order_id": 1001, "note": "gift" },
    { "order_id": 1002 }
  ]
}

Generated

1
2
3
4
5
6
7
8
9
10
type User struct {
	ID     int64   `json:"id"`
	Name   string  `json:"name"`
	Orders []Order `json:"orders"`
}

type Order struct {
	OrderID int64   `json:"order_id"`
	Note    *string `json:"note"`
}

2 Details that matter in Go

Tags on every field

Go only exports capitalized names. encoding/json matches keys case-insensitively, so name finds Name, but created_at never finds CreatedAt, and without a tag that field silently stays at its zero value. Tags make every mapping explicit.

Initialisms in caps

ID, URL, API, HTTP, UUID. Go's style guide asks for this, and linters such as golint and staticcheck flag Id and Url.

Pointers for nullable

Use pointers when absent and zero mean different things. A *string can be nil, which is the only way to tell a missing field from an empty one. That matters for a PATCH body and not for a read-only response, so it is a toggle.

int64 over int

int is platform-sized. A JSON id that fits on your laptop can overflow on a 32-bit target, so whole numbers come out as int64.

3 omitempty is not a null check

omitempty only affects encoding, and it drops any value that is the zero value for its type. A deliberate false, an intentional 0 and an empty string all vanish from the output along with the fields you meant to omit. If "absent" and "set to zero" mean different things in your API, use a pointer and leave omitempty off.

How to use the JSON to Go Struct tool

  1. Paste a JSON sample into the left panel.
  2. Set Root name for the top-level struct.
  3. Tick Pointers for nullable when you need to tell an absent field from a zero value, which matters for PATCH requests and not much else.
  4. Tick Add omitempty if the struct is for encoding rather than decoding.
  5. Copy Result, or Download to save it as types.go.

How to decode JSON into a struct in Go

Go

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
)

type User struct {
    ID     int64   `json:"id"`
    Name   string  `json:"name"`
    Note   *string `json:"note"`           // nil means the key was absent or null
    Orders []Order `json:"orders"`
}

type Order struct {
    OrderID int64   `json:"order_id"`
    Total   float64 `json:"total"`
}

func main() {
    res, err := http.Get("https://api.example.com/user/1")
    if err != nil {
        panic(err)
    }
    defer res.Body.Close()

    var u User
    // Decode ignores unknown fields by default. To reject them:
    //   dec := json.NewDecoder(res.Body); dec.DisallowUnknownFields()
    if err := json.NewDecoder(res.Body).Decode(&u); err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", u)
}

Decode silently ignores fields the struct does not mention. That is usually what you want from an API you do not control, and it is also how a typo in a tag can go unnoticed. Call DisallowUnknownFields() on the decoder when you want those reported.

When a field can be more than one type

// Some APIs send an id as a number sometimes and a string other times.
type ID struct{ Value string }

func (i *ID) UnmarshalJSON(b []byte) error {
    // try string first
    var s string
    if err := json.Unmarshal(b, &s); err == nil {
        i.Value = s
        return nil
    }
    var n json.Number
    if err := json.Unmarshal(b, &n); err != nil {
        return fmt.Errorf("id: expected string or number, got %s", b)
    }
    i.Value = n.String()
    return nil
}

// json.RawMessage is the other escape hatch: keep the bytes,
// decide how to decode them once you have read a discriminator field.

Common errors and how to fix them

Every field is empty after decoding
The struct fields are not exported. encoding/json can only see capitalized names, so name must be Name with a json:"name" tag.
json: cannot unmarshal string into Go value of type int64
The API is sending the number as a quoted string. Add ,string to the tag: `json:"id,string"`, or decode into json.Number.
A deliberate false or 0 disappears from the output
omitempty drops zero values, and false and 0 are zero values. Use a pointer for those fields and leave omitempty off.
golint complains about Id and Url
Go style capitalizes initialisms: ID, URL, API, HTTP. The generator already does this; the warning usually comes from a struct written by hand.
Large IDs come back slightly wrong
Decoding into interface{} or float64 loses precision above 2^53. Use int64, or json.Number if the value might not be an integer.

Good to know

int is platform-sized: 64 bits on most machines today, 32 on some embedded and WebAssembly targets. A JSON id that decodes fine on your laptop can overflow elsewhere, so the generator emits int64 for whole numbers. If you know the range is small, narrowing it by hand is safe and slightly cheaper.

Specifications and references

This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.

Frequently Asked Questions

Are JSON tags included?

Yes, on every field. Go only exports capitalized names, and encoding/json matches keys case-insensitively, so name finds Name without help. However, a snake_case key such as created_at never matches CreatedAt without a tag, and that field silently stays at its zero value. The tags make every mapping explicit.

How are field names converted?

snake_case and kebab-case become PascalCase, and common initialisms are capitalized the way Go's style guide asks: ID, URL, API and HTTP, not Id, Url, Api and Http. Linters such as golint and staticcheck flag the alternatives.

Which numeric type is used?

int64 when every sample value is a whole number, float64 when any of them has a decimal point. JSON has one number type, so this is inferred from your sample. Check the field if its range matters.

What about fields that can be null?

Tick Pointers for nullable and nullable fields become *string, *int64 and so on. A pointer is the only way to tell absent from zero in Go, which matters for a PATCH endpoint and not for a read-only response. A field that is null in every sample has no type to infer and comes out as interface{}.

Why is the top-level struct named Root?

The top-level struct is named Root by default here, because JSON has no name for the top-level object. Type a better name in the Root name field. Nested structs are named after the keys they came from.

Should I add omitempty?

Only if you are encoding. On the way out, omitempty drops zero values, which is often what you want and occasionally hides a deliberate false or 0. It has no effect when decoding.

Related

The same JSON also becomes a TypeScript interface or a Python dataclass. For a contract you can validate data against, see the JSON Schema generator.

More Data Converters

Browse all data converters

Copied to clipboard!