Building API servers in Go involves a fair amount of sending JSON data as response. “Encode” is just a fancy term that means convert into a particular form. In our case, we will be converting from Go struct types into JSON format.
Converting Go struct to JSON happens when we have to respond with a payload back to an HTTP Request (GET/ POST) i.e. getting data from a database, do some calculations with it, and return it back to the client.
Go’s standard library provides a helpful package called encoding/json to achieve this encoding in particular.
Basic sample
Here we can see a simple struct:
type User struct {
ID uuid.UUID
FirstName string
LastName string
MiddleName string
CreatedAt time.Time
UpdatedAt time.Time
}
If we just “encode” this struct to JSON using the standard library encoding/json package, the output will look like this:
❯ curl localhost:4000/v1/users/019b87ef-b562-7d67-b00b-8940309795de
{
"ID": "019b87ef-b562-7d67-b00b-8940309795de",
"FirstName": "John",
"LastName": "Doe",
"MiddleName": "M",
"CreatedAt": "2026-01-04T15:38:53.968434+08:00",
"UpdatedAt": "2026-01-04T15:38:53.968434+08:00"
}
This works but the convention might be unnatural for your API users. With the help of JSON struct tags, we now have the flexibility to control the convention (camelCase, snake_case, etc), omit data, and maybe stringify some fields before sending back to the user.
Customize JSON by struct tags
We will see just how capable Go’s JSON encoding package is. First of the list the ability to change the convention and how it will appear as a JSON object!
type User struct {
ID uuid.UUID `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
MiddleName string `json:"middle_name"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
user := data.User{
ID: userID,
FirstName: "John",
LastName: "Doe",
MiddleName: "M",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
❯ curl -i localhost:4000/v1/users/019b87ef-b562-7d67-b00b-8940309795de
{
"id": "019b87ef-b562-7d67-b00b-8940309795de",
"first_name": "John",
"last_name": "Doe",
"middle_name": "M",
"created_at": "2026-01-11T18:03:09.991181+08:00",
"updated_at": "2026-01-11T18:03:09.991181+08:00"
}
IMPORTANT: struct fields must be exported (starts with capital letter). Any fields which are not exported will be ignored when encoding a struct to JSON
type User struct {
id uuid.UUID `json:"id"`
...
}
❯ curl localhost:4000/v1/users/019b87ef-b562-7d67-b00b-8940309795de
{
"first_name": "John",
"last_name": "Doe",
"middle_name": "M",
"created_at": "2026-01-04T15:38:53.968434+08:00",
"updated_at": "2026-01-04T15:38:53.968434+08:00"
}
Hiding fields in the JSON object
Sometimes, we just don’t want to include a struct field AT ALL. And there’s a very neat way to do this by using the - (hyphen) directive.
type User struct {
MiddleName string `json:"-"`
...
}
❯ curl localhost:4000/v1/users/019b87ef-b562-7d67-b00b-8940309795de
{
"id": "019b87ef-b562-7d67-b00b-8940309795de",
"first_name": "John",
"last_name": "Doe",
"created_at": "2026-01-04T15:42:39.592092+08:00",
"updated_at": "2026-01-04T15:42:39.592092+08:00"
}
Other times, there are just optional fields and we can also leave it out if there’s no value. Go has a nice default zero-value rule for its types when you don’t provide one. See the spec.
If a field is an optional field which sometimes has no value. As of Go 1.24, we can use omitzero directive to hide this field from the JSON output.
Here’s a sample on no-value field:
user := data.User{
ID: userID,
FirstName: "John",
LastName: "Doe",
// MiddleName is omitted to demonstrate default behavior
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
❯ curl -i localhost:4000/v1/users/019b87ef-b562-7d67-b00b-8940309795de
{
"id": "019b87ef-b562-7d67-b00b-8940309795de",
"first_name": "John",
"last_name": "Doe",
"middle_name": "",
"created_at": "2026-01-04T16:18:49.2911+08:00",
"updated_at": "2026-01-04T16:18:49.2911+08:00"
}
Adding omitzero hides zero-value field
type User struct {
MiddleName string `json:"middle_name,omitzero"`
...
}
user := data.User{
ID: userID,
FirstName: "John",
LastName: "Doe",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
Notice that instead of empty string, the field was removed altogether
❯ curl -i localhost:4000/v1/users/019b87ef-b562-7d67-b00b-8940309795de
{
"id": "019b87ef-b562-7d67-b00b-8940309795de",
"first_name": "John",
"last_name": "Doe",
"created_at": "2026-01-04T16:24:26.914947+08:00",
"updated_at": "2026-01-04T16:24:26.914947+08:00"
}
With this directive, we have an option to reduce our payload size by omitting empty/zero-value fields.
The string directive
Sometimes we also have to provide quality of life to our API users and that maybe in the form of string-formatted output to reduce the burden of them transforming a specific field to string.
Using the string directive to force the output to be string and it works on int*,uint*,float* or bool types
Default bool behavior:
type User struct {
IsActive bool `json:"is_active"`
...
}
user := data.User{
ID: userID,
FirstName: "John",
LastName: "Doe",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
{
"id": "019b87ef-b562-7d67-b00b-8940309795de",
"first_name": "John",
"last_name": "Doe",
"is_active": false,
"created_at": "2026-01-04T16:46:47.141081+08:00",
"updated_at": "2026-01-04T16:46:47.141082+08:00"
}
But using string directive:
type User struct {
IsActive bool `json:"is_active,string"`
...
}
user := data.User{
ID: userID,
FirstName: "John",
LastName: "Doe",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
{
"id": "019b87ef-b562-7d67-b00b-8940309795de",
"first_name": "John",
"last_name": "Doe",
"is_active": "false",
"created_at": "2026-01-04T16:47:11.037533+08:00",
"updated_at": "2026-01-04T16:47:11.037533+08:00"
}
Key Takeaways
- You can return a Go struct as raw as it is but you don’t have to.
- Go’s JSON encoding package can help you transform and control what the data will look like
- With Go’s zero-value defaults and
omitzero, we can make our payload size leaner if that’s acceptable to not return empty fields - Stringifying a JSON field is as quick as just adding a
stringdirective