YamlBox

Code generators · 5 min read

JSON to Go: structs that unmarshal on the first try

Go makes you write down the shape of the JSON you are decoding, and the compiler holds you to it. A JSON to Go conversion from a real response removes both the typing and the guesswork: field names, `json` tags and nested types arrive correct, and you can go straight to the part of the program that actually does something.

Updated

The generated shape

A converter turns each JSON object into a struct, each array into a slice, and infers a Go type per value. Nested objects become named types rather than anonymous blobs, which keeps the signature of your decoding code readable:

                type Container struct {
  Name  string `json:"name"`
  Image string `json:"image"`
  Ports []struct {
    ContainerPort int `json:"containerPort"`
  } `json:"ports"`
}
              

The `json` tags are the important part. Go’s decoder is case-insensitive when matching field names, but `containerPort` and `ContainerPort` do not align without a tag, and any key that is not a valid Go identifier — `app.kubernetes.io/name`, for instance — cannot be matched at all without one.

Numbers, nulls and the two classic traps

  • Numbers — inference produces `int` for whole values, but JSON has no integer type. A field that is `3` today may be `3.5` tomorrow, so IDs and money are safer as `float64`, `string` or `json.Number` depending on your needs.
  • Nulls — a key that can be `null` should be a pointer (`*string`) so you can tell “absent” from “empty”.
  • Timestamps — an RFC 3339 string decodes cleanly into `time.Time`; a Unix epoch number does not, so keep those as `int64` and convert explicitly.
  • Unknown fields — `json.Decoder` ignores extras by default; use `DisallowUnknownFields` when you would rather fail loudly.

Generate from two or three responses when you can. Fields that are sometimes missing become pointers, and that decision is much easier to make from evidence than from memory.

From struct to working code

With the structs in place, decoding is three lines and the compiler enforces the access paths from there. A common pattern is to decode into the generated struct, then map into your domain type so that the JSON shape can change without rippling through the application. Tag the generated file with the endpoint it came from, and regenerate when the response changes rather than editing it by hand — hand-edited generated code is the version that nobody dares to regenerate later.

Generate locally

The payloads worth generating Go structs from are usually real API responses, and real responses carry tokens and customer data. A browser-based JSON to Go conversion keeps the payload in the tab — no upload, no retention question — which makes generating from production data a normal step rather than a policy exception.

JSON to Go — frequently asked questions

Why are my Go structs not decoding JSON?

Field names in JSON rarely match Go’s exported names exactly, so the struct needs `json` tags. Regenerate with tags enabled, confirm the key spelling including punctuation, and check whether the decoder should allow unknown fields.

Should generated numeric fields be int or float64?

Use `float64` — or `json.Number` when precision matters — for anything from an external API, because JSON does not distinguish integers from decimals. Reserve `int` for values you control end to end.

How do I handle a field that is sometimes null?

Make it a pointer type such as `*string`, which encodes both “key absent” and “key present with null value” while still allowing you to check for a real value. Non-pointer fields silently become zero values instead.

Tools mentioned in this post

Browse all 19 tools →

Keep reading

All 19 posts →