Go Struct JSON Tags Not Serializing Correctly
When a Go struct doesn’t serialize to JSON the way you expect — fields missing entirely, wrong key names, or values that should be omitted but aren’t — the cause is almost always a small detail in how the struct fields or their tags are written, since Go’s JSON package is strict about a few rules that aren’t always obvious from the syntax alone.
The Problem
You define a struct with JSON tags and marshal it, expecting a specific JSON shape, but the output doesn’t match:
type User struct {
name string `json:"name"`
Email string `json:"email"`
}
data, _ := json.Marshal(user)
fmt.Println(string(data))
// {"email":"user@example.com"} -- "name" is missing entirely
Sometimes the opposite problem shows up instead: fields you expected to be omitted when empty still appear in the output as "" or 0, cluttering the JSON with values that should have been left out.
Why It Happens
Go’s encoding/json package follows a strict rule that trips up a lot of people coming from other languages: only exported (capitalized) struct fields can be marshaled to or from JSON at all, regardless of what tag you put on them. Beyond that, common causes include:
- The field name starts with a lowercase letter, making it unexported — the JSON tag is silently ignored in this case, because the
encoding/jsonpackage can’t access unexported fields through reflection at all - A typo in the tag syntax, like a missing colon or extra space, which Go doesn’t error on — it just fails to apply the tag as intended, often falling back to the field’s default name
- Missing the
omitemptyoption when you want zero-value fields left out of the output, since JSON tags without it always include the field regardless of its value - Using
omitemptyon a field type where “empty” doesn’t mean what you expect — for example, a pointer versus a value type behaves differently, and a struct type has no true “empty” value thatomitemptyrecognizes at all
The Fix
First, make sure every field you want in the JSON output is exported (capitalized), since this is a hard requirement, not a style preference:
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
Double-check tag syntax carefully — the format is a colon-separated key-value pair inside backticks, with no space between the field name and the colon:
// Correct:
Name string `json:"name"`
// Incorrect (extra space breaks the tag):
Name string `json: "name"`
To omit empty fields from the output, add omitempty after the field name, separated by a comma:
type User struct {
Name string `json:"name"`
Nickname string `json:"nickname,omitempty"`
}
Be aware that omitempty considers a field “empty” based on its zero value for that type — an empty string, zero number, nil pointer/slice/map, or false boolean. It does not consider a struct value empty even if all its fields are zero values, so omitempty on a nested struct field typically has no effect unless you use a pointer to that struct instead.
If you need to debug exactly what tag Go is seeing, you can inspect it directly using reflection, which is useful when a typo is suspected but not obvious by eye:
t := reflect.TypeOf(User{})
field, _ := t.FieldByName("Name")
fmt.Println(field.Tag.Get("json"))
Still Not Working?
If fields are exported and tags look correct but values still aren’t appearing, check whether you’re marshaling a pointer to a zero-value struct versus an actual populated instance — a common mistake is passing an uninitialized struct into a function that expects data to already be filled in, which produces valid but entirely empty-looking JSON that can be mistaken for a tag configuration problem.