Encoding Structs
In this chapter we’re going to head back to the showMovieHandler
method that we made earlier and update it to return a JSON response which represents a single movie in our system. Similar to this:
{ "id": 123, "title": "Casablanca", "runtime": 102, "genres": [ "drama", "romance", "war" ], "version": 1 }
Instead of encoding a map to create this JSON object (like we did in the previous chapter), this time we’re going to encode a custom Movie
struct.
So, first things first, we need to begin by defining a custom Movie
struct. We’ll do this inside a new internal/data
package, which later will grow to encapsulate all the custom data types for our project along with the logic for interacting with our database.
If you’re following along, go ahead and create a new internal/data
directory containing a movies.go
file:
$ mkdir internal/data $ touch internal/data/movies.go
And in this new file let’s define the custom Movie
struct, like so:
package data import ( "time" ) type Movie struct { ID int64 // Unique integer ID for the movie CreatedAt time.Time // Timestamp for when the movie is added to our database Title string // Movie title Year int32 // Movie release year Runtime int32 // Movie runtime (in minutes) Genres []string // Slice of genres for the movie (romance, comedy, etc.) Version int32 // The version number starts at 1 and will be incremented each // time the movie information is updated }
Now that’s done, let’s update our showMovieHandler
to initialize an instance of the Movie
struct containing some dummy data, and then send it as a JSON response using our writeJSON()
helper.
It’s quite simple in practice:
package main import ( "fmt" "net/http" "time" // New import "greenlight.alexedwards.net/internal/data" // New import ) ... func (app *application) showMovieHandler(w http.ResponseWriter, r *http.Request) { id, err := app.readIDParam(r) if err != nil { http.NotFound(w, r) return } // Create a new instance of the Movie struct, containing the ID we extracted from // the URL and some dummy data. Also notice that we deliberately haven't set a // value for the Year field. movie := data.Movie{ ID: id, CreatedAt: time.Now(), Title: "Casablanca", Runtime: 102, Genres: []string{"drama", "romance", "war"}, Version: 1, } // Encode the struct to JSON and send it as the HTTP response. err = app.writeJSON(w, http.StatusOK, movie, nil) if err != nil { app.logger.Error(err.Error()) http.Error(w, "The server encountered a problem and could not process your request", http.StatusInternalServerError) } }
OK, let’s give this a try!
Restart the API and then visit localhost:4000/v1/movies/123
in your browser. You should see a JSON response which looks similar to this:

There are a few interesting things in this response to point out:
Our
Movie
struct has been encoded into a single JSON object, with the field names and values as the key/value pairs.By default, the keys in the JSON object are equal to the field names in the struct (
ID
,CreatedAt
,Title
and so on). We’ll talk about how to customize these shortly.If a struct field doesn’t have an explicit value set, then the JSON-encoding of the zero value for the field will appear in the output. We can see an example of this in the response above — we didn’t set a value for the
Year
field in our Go code, but it still appears in the JSON output with the value0
.
Changing keys in the JSON object
One of the nice things about encoding structs in Go is that you can customize the JSON by annotating the fields with struct tags.
Probably the most common use of struct tags is to change the key names that appear in the JSON object. This can be useful when your struct field names aren’t appropriate for public-facing responses, or you want to use an alternative casing style in your JSON output.
To illustrate how to do this, let’s annotate our Movies
struct with struct tags so that it uses snake_case
for the keys instead. Like so:
package data ... // Annotate the Movie struct with struct tags to control how the keys appear in the // JSON-encoded output. type Movie struct { ID int64 `json:"id"` CreatedAt time.Time `json:"created_at"` Title string `json:"title"` Year int32 `json:"year"` Runtime int32 `json:"runtime"` Genres []string `json:"genres"` Version int32 `json:"version"` }
And if you restart the server and visit localhost:4000/v1/movies/123
again, you should now see a response with snake_case
keys similar to this:

Hiding struct fields in the JSON object
It’s also possible to control the visibility of individual struct fields in the JSON by using the omitempty
and -
struct tag directives.
The -
(hyphen) directive can be used when you never want a particular struct field to appear in the JSON output. This is useful for fields that contain internal system information that isn’t relevant to your users, or sensitive information that you don’t want to expose (like the hash of a password).
In contrast the omitempty
directive hides a field in the JSON output if and only if the struct field value is empty, where empty is defined as being:
- Equal to
false
,0
, or""
- An empty
array
,slice
ormap
- A
nil
pointer or anil
interface value
To demonstrate how to use these directives, let’s make a couple more changes to our Movie
struct. The CreatedAt
field isn’t relevant to our end users, so let’s always hide this in the output using the -
directive. And we’ll also use the omitempty
directive to hide the Year
, Runtime
and Genres
fields in the output if and only if they are empty.
Go ahead and update the struct tags like so:
package data ... type Movie struct { ID int64 `json:"id"` CreatedAt time.Time `json:"-"` // Use the - directive Title string `json:"title"` Year int32 `json:"year,omitempty"` // Add the omitempty directive Runtime int32 `json:"runtime,omitempty"` // Add the omitempty directive Genres []string `json:"genres,omitempty"` // Add the omitempty directive Version int32 `json:"version"` }
Now when you restart the application and refresh your web browser, you should see a response which looks exactly like this:

We can see here that the CreatedAt
struct field no longer appears in the JSON at all, and the Year
field (which had the value 0
) doesn’t appear either thanks to the omitempty
directive. The other fields that we used omitempty
on (Runtime
and Genres
) are unaffected.
Additional Information
The string struct tag directive
A final, less-frequently-used, struct tag directive is string
. You can use this on individual struct fields to force the data to be represented as a string in the JSON output.
For example, if we wanted the value of our Runtime
field to be represented as a JSON string (instead of a number) we could use the string
directive like this:
type Movie struct { ID int64 `json:"id"` CreatedAt time.Time `json:"-"` Title string `json:"title"` Year int32 `json:"year,omitempty"` Runtime int32 `json:"runtime,omitempty,string"` // Add the string directive Genres []string `json:"genres,omitempty"` Version int32 `json:"version"` }
And the resulting JSON output would look like this:
{ "id": 123, "title": "Casablanca", "runtime": "102", ← This is now a string "genres": [ "drama", "romance", "war" ], "version": 1 }
Note that the string
directive will only work on struct fields which have int*
, uint*
, float*
or bool
types. For any other type of struct field it will have no effect.