JSON to Go: structs with json tags
Go's encoding/json package decodes JSON into structs, but only into exported fields, and it relies on struct tags when the Go field name differs from the JSON key. This JSON to Go generator writes those structs from a sample: exported PascalCase fields, a json:"..." tag on every field, one named struct per object shape, and Go types chosen from the values.
Example
{
"id": 42,
"title": "Write docs",
"done": false,
"priority": 2.5,
"tags": ["docs"],
"owner": { "name": "Ada", "email": null },
"due": "2026-10-01T09:00:00Z",
"subtasks": [
{ "title": "Outline", "estimate": 2 },
{ "title": "Draft" }
]
}
type Task struct {
ID int64 `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
Priority float64 `json:"priority"`
Tags []string `json:"tags"`
Owner Owner `json:"owner"`
Due time.Time `json:"due"`
Subtasks []Subtask `json:"subtasks"`
}
type Owner struct {
Name string `json:"name"`
Email interface{} `json:"email"`
}
type Subtask struct {
Title string `json:"title"`
Estimate *int64 `json:"estimate,omitempty"`
}
Several Go idioms are visible. ID follows Go's initialism convention (not Id). The optional estimate is a pointer, *int64, so you can tell "missing" (nil) apart from zero, and its tag has omitempty so it is left out when encoding a nil value. time.Time decodes RFC 3339 strings automatically. The null-only email became interface{}.
Helpers in full mode
Without Types only, the output starts with package main, imports encoding/json, and adds two small helpers for the top-level type:
task, err := UnmarshalTask(data) // func UnmarshalTask([]byte) (Task, error)
bytes, err := task.Marshal() // func (r *Task) Marshal() ([]byte, error)
They are thin wrappers around json.Unmarshal and json.Marshal. Change the package name to match your code.
Type mapping
| JSON | Go |
|---|---|
| integer | int64 (*int64 if optional) |
| decimal | float64 |
| RFC 3339 date-time | time.Time |
| array | []T |
| object | named struct |
| null / unknown | interface{} |
| mixed-type values | a struct with one pointer field per possible type |
Decoding tips
For large responses, decode directly from the HTTP body with json.NewDecoder(resp.Body).Decode(&task) instead of reading everything into memory first. When numbers must keep full precision, for example IDs larger than 2^53 inside interface{} values, call decoder.UseNumber() so they arrive as json.Number rather than float64. And remember that encoding/json silently leaves fields at their zero value when a key is missing, which is another reason optional values are pointers in the generated structs.
Things to adjust
- Date-only strings such as
"2026-01-15"are not valid RFC 3339, so they are kept asstring;time.Timewould fail to decode them. - Large integers: if IDs exceed 2^53 and pass through JavaScript clients, APIs often send them as strings; keep them as strings in Go too, or use
json:",string". interface{}can be writtenanyin Go 1.18+.- Unknown keys are ignored by
encoding/json; useDecoder.DisallowUnknownFields()to reject them. - Mixed types generate wrapper structs that need custom
UnmarshalJSONmethods, which full mode includes. Prefer consistent JSON types where you control the API. - Shared shapes: objects with identical structure under different keys are merged into one struct type named after one of the keys. Rename it if the name is misleading; the json tags are unaffected.
Structs from XML are covered by XML to Go. Validate or pretty print your JSON with the JSON Formatter, and compare two payloads with JSON Diff. Generation runs locally in your browser; run gofmt on the result to align it with your editor's formatting.