Skip to tool
ecech.
💻 Developer & Code

JSON to Go Structs, Including Where You Need a Pointer to Tell 0 From Absent

Go has no undefined, so a missing number and a zero look identical after Unmarshal. This generates the tags, exports every field, and says where a pointer is needed.

Go



Decisions worth checking

Advertisement

How the calculation works

One capital letter decides whether the field is filled in lowercase type User struct {   name string } unexported \u2014 json never sets it exported, with a tag type User struct {   Name string `json:"name"` } filled in, and keeps the JSON key No error either way. The lowercase version just quietly stays empty.

How to Use This Tool

Paste a response and get structs. Read the decisions panel, which lists the places one sample could not settle.

Exported names and tags are not optional

Go's encoding/json uses reflection, and reflection cannot set unexported fields. A field called name is invisible to it; a field called Name is not. Unmarshal a response into a struct with lowercase fields and you get no error and no data — every field sits at its zero value.

So every field here is capitalised, and each carries a json:"original_key" tag so the wire format is preserved. Without the tag, Go matches case-insensitively on the field name, which happens to work for name and fails for user_id, because UserId does not match user_id once the underscore is gone.

Where you need a pointer

Go has no undefined. Every type has a zero value, and after unmarshalling you cannot tell these apart:

  • "retry_count": 0 — explicitly zero
  • "retry_count" absent entirely
  • "retry_count": null

All three leave an int field holding 0. If your code treats 0 as "no retries configured" and the API omits the key when it means "use the default", those are different situations you can no longer distinguish.

The fix is *int: nil means absent or null, and a pointer to 0 means explicitly zero. It costs an allocation and a nil check at every use, which is why you want it only where the distinction matters — so this flags the fields where the sample shows a null, rather than making everything a pointer.

Three inputs, one indistinguishable result "count": 0 0 "count": null 0 (key absent) 0 int *int tells all three apart Use a pointer only where the difference changes behaviour. Everywhere else it is noise.
Go's zero values are convenient until absence has to mean something.

No union types

A key that is a string in one array element and a number in another has no Go type. It becomes interface{} (or any in modern Go), which pushes a type switch to every place that reads it. That is worth knowing before you write the code rather than at the first failed type assertion, so it is flagged.

If you control the API, this is a bug in the API. If you do not, a custom UnmarshalJSON on a named type is the usual way to normalise it at the boundary.

Two more things worth checking

  • Large integers. Go's int64 handles 19-digit IDs fine, which JavaScript cannot — but if the JSON was produced by a JavaScript service, the value may already have been corrupted before it reached you. A string field for IDs is safer at the boundary.
  • Timestamps. encoding/json parses RFC 3339 into time.Time automatically, so an ISO 8601 string with an offset can be typed as time.Time directly. Anything else — a Unix integer, a date with no offset — needs a custom unmarshaller, so it stays a string here and is flagged.
Advertisement

Frequently Asked Questions

Why is my Go struct empty after unmarshalling JSON?
Almost always because the fields are unexported. encoding/json uses reflection, which cannot set fields starting with a lowercase letter, so it silently leaves them at their zero value with no error. Capitalise every field and add a json tag carrying the original key.
Do I need json tags if the field names match?
Usually yes. Without a tag Go matches case-insensitively on the field name, which works for a key like name but fails for user_id, because the idiomatic Go name UserID has no underscore and will not match. Tags also protect you if the API renames a key.
When should a Go struct field be a pointer?
When you need to tell an absent or null value apart from a legitimate zero. Go has no undefined, so an int that was missing, explicitly null, or explicitly 0 all end up as 0. A *int distinguishes them at the cost of a nil check everywhere, so use it only where the difference changes behaviour.
What does omitempty actually do?
It affects marshalling only, not unmarshalling. When encoding, a field with its zero value is left out of the output entirely. That is often what you want for optional fields, and it is wrong when the receiver needs to see an explicit false or 0 — in which case use a pointer instead.
What happens when a JSON key has two different types?
Go has no union type, so the field has to be interface{} and every read needs a type switch. If you control the API this is worth fixing there; if not, a named type with a custom UnmarshalJSON method is the usual way to normalise it at the boundary rather than throughout your code.
Can Go handle large ID numbers from JSON?
Yes, int64 covers 19-digit IDs without loss, unlike JavaScript. But if the JSON came from a JavaScript service the value may already have been corrupted before it reached you, so a string field for identifiers is safer at the boundary regardless of what Go can hold.
Is my JSON uploaded anywhere?
No. It is parsed and converted in your browser, which matters because API samples routinely contain tokens and personal data.

Related tools in Developer & Code

Browse all Developer & Code tools
The person who builds ecech., at the desk where the tools are written.

Made by one person

ecech. is not a content farm. Every tool here is written and checked by hand, one at a time, by someone who wanted the tool to exist and could not find a version that showed its working.

No accounts and no sign-in, and nothing you type reaches a server — every calculation on this page runs inside your browser. The ads are served by Google and do set their own cookies, which is set out in full on the privacy page. More about the site.