This tool generates Go struct definitions from a sample JSON value in your browser; nothing leaves your machine. The generated code is a starting point — it captures the shape of the JSON, not the rules behind it.
The basic mapping
JSON:
{
"name": "Alice",
"age": 30,
"active": true
}
Generated Go:
type Root struct {
Name string `json:"name"`
Age int `json:"age"`
Active bool `json:"active"`
}
Three things happen:
- Field name. PascalCase Go identifier derived from the JSON key.
name→Name. Required because Go only marshals/unmarshals exported (capitalized) fields. - Type. Inferred from the JSON value. String →
string, integer-looking number →int, decimal-looking number →float64, boolean →bool, null →*T(pointer to the inferred type), object → nested struct, array → slice. - Struct tag.
json:"name"tellsencoding/jsonthe original JSON key. Without it, Go marshals the Go field name (Name), not the JSON key (name).
Number type inference
The trickiest mapping. JSON has a single numeric type; Go has many.
| JSON | Inferred Go |
|---|---|
42 | int |
42.0 | float64 (the trailing .0 signals fractional) |
42.5 | float64 |
1e10 | float64 (exponential) |
9999999999 (large) | int64 or float64 (depending on tool) |
The trap: if the sample has 42 but the production data sometimes has 42.5, the generated int field will fail to unmarshal the decimal. Generators based on a single sample can’t know the full range.
Defensive choice: use float64 whenever the field is “number-typed” rather than “definitely-integer.” Tightens to int only when you’re sure.
Optional fields and pointers
JSON’s null is awkward in Go’s static type system. Two conventions:
// Pointer to type — distinguishes null/absent from zero value
type User struct {
Email *string `json:"email"`
}
// Type with omitempty — drops empty values when marshaling
type User struct {
Email string `json:"email,omitempty"`
}
The pointer form distinguishes “key is null” from “key is missing” from “key is empty string.” The omitempty form treats empty/zero as equivalent to absent.
For round-trip-safe deserialization, prefer the pointer form. For producing JSON, omitempty is more idiomatic Go.
The tool above can’t infer a type from null, so a null field becomes interface{}; replace it with the pointer form (*string etc.) once you know the real type. Keys that aren’t valid Go names are converted (user_id → UserId, 2fa → X2fa) and the original key stays in the json: tag.
Nested objects → nested structs
JSON:
{
"user": {
"name": "Alice",
"address": {
"city": "NYC"
}
}
}
Generated Go:
type Root struct {
User User `json:"user"`
}
type User struct {
Name string `json:"name"`
Address Address `json:"address"`
}
type Address struct {
City string `json:"city"`
}
Each nested object becomes its own type. Some generators inline anonymous structs:
type Root struct {
User struct {
Name string `json:"name"`
Address struct {
City string `json:"city"`
} `json:"address"`
} `json:"user"`
}
Anonymous structs are compact for one-off use; named structs are easier to test, mock, and reuse.
Arrays
JSON: [1, 2, 3]
Go: []int
JSON: [{"name": "Alice"}]
Go: []struct { Name string `json:"name"` }
For an array of objects, the inferred type is []<element type>. The element type is inferred from the first array element (or the union of elements, in better tools).
What generation can’t infer
- Required vs optional. If the sample has
name, is it always present, or did this row just happen to include it? The tool can’t know without multiple samples. - Validation rules.
emailis a string; the tool can’t infer that it should match an email regex. - Enums. A field whose value is one of
"draft","published","archived"is best modeled as a Go enum, not as astring. The tool emitsstring. - Numeric ranges. A field that always holds 0–100 is generated as
intwith no constraint. The tool can’t tell. - Polymorphism. A
datafield that holds different shapes depending on atypefield needs custom unmarshaling logic. Generators emit a struct with all fields nullable, which is a workable but lossy approximation.
These all require human review of the generated types. Treat the output as a draft.
Struct tags beyond json:
Modern Go projects often have multiple tag families:
type User struct {
ID int `json:"id" db:"user_id" validate:"required"`
Email string `json:"email" db:"email" validate:"required,email"`
}
db: for SQL ORM mapping (sqlx, similar). validate: for input validation libraries. The tool above generates only json: tags; add others manually if your project uses them.
Common pitfalls
- Generating from one sample. Capture variations: with and without optional fields, with
nulland without, with empty arrays, with the maximum-realistic field counts. Single-sample generation is brittle. - Treating large numbers as
int.intin Go is 32 or 64 bits depending on platform. For known 64-bit-needed values, useint64explicitly. - Re-running generation over hand-modified code. Lose hand-tuning. Generation is one-shot; treat the output as scaffold and check it into source control before customizing.
- Forgetting
omitemptywhen producing JSON. Without it, empty strings, zero numbers, and false booleans serialize as"",0, andfalserather than being omitted. Sometimes that’s correct; usually consumers want truly-optional fields omitted. - Renaming JSON keys. When the upstream JSON’s keys are snake_case, the generated tags say
json:"some_key". If you change the field name in Go, the tag doesn’t update — the JSON-side key is stillsome_key. Both have to be edited together.
For other JSON-to-language conversions, see JSON to TypeScript, JSON to Python dataclass.