Golang Anonymous Structs

Boston CartwrightBoston Cartwright | September 29, 2021 | golang
3 min read | ––– views
Photo by Arnaud Mesureur on Unsplash
Photo by Arnaud Mesureur on Unsplash

Taking advantage of type safety in small places

Want to check this out later?

Introduction

When I first started working in Go on web services, I was building types left and right to handle JSON data going in and out.

It was not uncommon for me to declare a number of structs at the top of a file, similar to this in an example car API:

type CarRequestBody struct {
  Make    string `json:"make"`
  Model   string `json:"model"`
  Color   string `json:"color"`
  Mileage string `json:"mileage"`
}

As my application grew, the number of structs I had did as well. They were needed to describe the expected json body to come in to, that would only be ever used in one handler.

However, once I learned about anonymous stucts, my code became a whole lot cleaner.

Anonymous struct basics

An anonymous struct is a struct that is associated to a variable immediatly and never given a type.

Here are some examples:

// without instantiation
var patient struct {
  FirstName string    `json:"firstName"`
  LastName  string    `json:"lastName"`
  DOB       time.Time `json:"dob"`
}

// instantiate immediately
newPatient := struct {
  FirstName string `json:"firstName"`
  LastName  string `json:"lastName"`
}{
  FirstName: "John",
  LastName:  "Doe",
}

These structs only exist in the scope they are defined. Contrasted with using a map[string]interface{}, the gains are obvious, especially considering type and scope safety.

If you find you need to use this exact struct in another place in your program, it's very easy to refactor it out to its own type.

When to use them

Unless I know I am going to use this struct in several places, I start with anonymous structs. When I need to use it somewhere else, I refactor it into a type at that point.

The most common place I use anonymous structs are in API handlers. Take the following for an example:

func createPatientHandler(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

  var body struct {
    FirstName string    `json:"firstName"`
    LastName  string    `json:"lastName"`
    DOB       time.Time `json:"dob"`
  }

  if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
    http.Error(
      w,
      fmt.Sprintf("unable to decode request body json: %s", err.Error()),
      http.StatusBadRequest,
    )
    return
  }

  createPatient(body.FirstName, body.LastName, body.DOB)
  // ... rest of the handler
}

This works great when considering that request bodies can be very unique to that handler.

What do you think? Let me know on Twitter @bstncartwright 😃