X Xerobit

JSON to Go Struct — Generate Golang Types from a JSON Sample

Paste JSON and get Go struct definitions with json tags. Handles nested structs, arrays, and null fields. Copy directly into your Go project and start unmarshalling.

Mian Ali Khalid · · 6 min read

Writing Go structs by hand from a JSON payload is one of the most repetitive tasks in Go development. Every field needs a name, a type, and a json:"fieldName" tag. A modest 15-field API response means 15 lines of struct definition, and that’s before you account for nested objects that each demand their own struct type. Miss a tag, capitalize the wrong letter, or make a typo in the tag string — and encoding/json silently drops the field during unmarshalling, leaving you with zero values and no error to chase down.

A JSON-to-Go struct generator eliminates this entirely. Paste your JSON sample, get back complete, compilable Go struct definitions with all tags populated correctly, ready to drop directly into your codebase.

Why Go struct tags are so tedious to write by hand

Go’s reflection-based JSON library requires struct field names to be exported (capitalized), but JSON keys are almost never capitalized. The json:"fieldName" tag bridges the gap — it tells encoding/json which JSON key maps to which Go field. Without the tag, Go falls back to case-insensitive matching, which works until it doesn’t: two fields that differ only in case, or a key like "URL" that would map to Url not URL, will quietly fail.

Every nested JSON object becomes a separate Go struct. Every array becomes a slice. If the API returns null for a field in some responses, you need a pointer type so your code can distinguish between “field was absent” and “field was zero”. Writing all of this by hand for every new API integration adds up fast — and the tedium is exactly the kind of work that produces bugs.

What a JSON-to-Go generator produces

The generator reads your JSON sample and applies a set of straightforward rules: JSON strings become string, JSON numbers become int or float64 depending on whether they contain a decimal, JSON booleans become bool, nested JSON objects become named struct types, and JSON arrays become slices. Every exported field gets the correct json:"key" tag derived from the original JSON key name.

Take this GitHub repository object:

{
  "repoName": "xerobit-dev",
  "stars": 142,
  "isPrivate": false,
  "owner": {
    "login": "MAK",
    "type": "User"
  },
  "topics": ["astro", "seo", "tools"]
}

The generator produces:

type Owner struct {
    Login string `json:"login"`
    Type  string `json:"type"`
}

type Repo struct {
    RepoName  string   `json:"repoName"`
    Stars     int      `json:"stars"`
    IsPrivate bool     `json:"isPrivate"`
    Owner     Owner    `json:"owner"`
    Topics    []string `json:"topics"`
}

The nested owner object becomes its own Owner struct, the topics array becomes []string, and every field name is converted to Go’s exported (PascalCase) convention while the original JSON key is preserved in the tag. This is precisely the output you’d write by hand — without the time or the risk of typos.

Using the generated struct in your Go code

Once you have the struct definition, unmarshalling is three lines:

import "encoding/json"

var repo Repo
err := json.Unmarshal([]byte(jsonData), &repo)
if err != nil {
    log.Fatal(err)
}
fmt.Println(repo.Owner.Login) // "MAK"

The json.Unmarshal call walks the JSON, matches keys to struct tags, and populates the fields. Access nested fields using dot notation (repo.Owner.Login), and iterate over slices normally (for _, topic := range repo.Topics). The generated struct is idiomatic Go — no third-party imports required for the basic case.

Handling null and optional fields

Real-world APIs rarely return perfectly consistent JSON. A field that’s a string in most responses might be null in others, or absent entirely. Go handles these cases with pointer types and struct tag options.

When a JSON field can be null, the generator (or you, when editing the output) should use a pointer type: *string, *int, *bool. A nil pointer means the field was null or missing; a non-nil pointer means the field was present and holds a value. Access it with a nil check before dereferencing.

For fields that are sometimes omitted from the JSON response entirely, add omitempty to the struct tag:

Email *string `json:"email,omitempty"`

The omitempty option affects marshalling (encoding Go → JSON): if the field is nil or a zero value, the key is omitted from the output. It has no effect on unmarshalling — a missing key in the incoming JSON simply leaves the Go field at its zero value or nil.

To explicitly ignore a field during both marshalling and unmarshalling, use the blank tag:

InternalField string `json:"-"`

For truly dynamic JSON where you don’t know the keys ahead of time, embed a map[string]interface{} or use json.RawMessage to defer parsing of a subtree.

Common type-mapping gotchas

JSON’s number type is ambiguous — it covers both integers and floating-point values. When a generator sees 142, it produces int. When it sees 3.14, it produces float64. But when an API returns a large ID like a Twitter-style snowflake ID (1234567890123456789), the generator might infer int64 — and you need int64, not int, because int is 32-bit on 32-bit platforms and overflows values above ~2.1 billion. Always verify the inferred type against the actual range of values the API documents.

JSON arrays of mixed types — [1, "two", true] — have no clean Go equivalent. The generator produces []interface{}, which forces type assertions at runtime:

val := topics[0].(string)

This is fragile. If the API guarantees a uniform type, override the generated []interface{} with the correct slice type manually.

Struct tags beyond JSON

A generated Go struct is often the starting point for more than JSON handling. If you’re storing the same struct in a PostgreSQL database using sqlx, you’ll add db:"column_name" tags alongside the json tags. If you’re validating incoming request bodies, you’ll add validate:"required,email" tags for a library like go-playground/validator. The generator gives you the foundation; augmenting it with additional tags is a natural next step and is much faster than building from scratch.

Complementary tools in the Go JSON ecosystem

The standard library’s encoding/json handles the vast majority of use cases. For high-throughput services where JSON parsing is a measurable bottleneck, json-iterator/go is a drop-in replacement that uses the same API but runs significantly faster. For cases where you need to read a single field from a large JSON blob without unmarshalling the whole thing, gjson provides path-based querying. And for situations where you’re working with map[string]interface{} data (common when dealing with arbitrary config), mapstructure decodes maps into typed structs using reflection.

Before running any of these against a JSON sample, validate the JSON first. A missing comma or unmatched bracket produces silently wrong generator output — the generator will either error or misinterpret the structure. Use the JSON Formatter to confirm your sample is valid before generating structs from it.

The workflow is simple: validate JSON, generate the struct, review the inferred types for edge cases like large integers or nullable fields, then add any additional struct tags your project needs. What used to take five minutes of careful typing takes five seconds.

Written by Mian Ali Khalid. Last updated 2026-05-12.


Related posts

Written by Mian Ali Khalid. Part of the Data & Format pillar.