To encode JSON data, the encoding/json package provides the Marshal function, which has the following signature:
func Marshal(v interface{}) ([]byte, error)
This function takes one parameter, which is of type interface, so pretty much any object you can think of since interface represents any type in Go. It returns a tuple of ([]byte, error), you will see this return style quite frequently in Go, some languages implement a try catch approach that encourages an error to be thrown when an operation cannot be performed, Go suggests the pattern (return type, error), where the error is nil when an operation succeeds.
In Go, unhandled errors are a bad thing, and whilst the language does implement Panic and Recover, which resemble exception handling in other languages, the situations where you should use these are quite different (see The Go Programming Language, Kernaghan). In Go, the panic function causes normal execution to stop and all deferred function calls in the Go routine are executed, the program will then crash with a log message. It is generally used for unexpected errors that indicate a bug in the code and good robust Go code will attempt to handle these runtime exceptions and return a detailed error object back to the calling function.
This pattern is exactly what is implemented with the Marshal function. In the instance that Marshal cannot create a JSON encoded byte array from the given object, which could be due to a runtime panic, then this is captured and an error object detailing the problem is returned to the caller.
Let's try this out, expanding on our existing example, instead of simply printing a string from our handler, let's create a simple struct for the response and return this instead.
Example 1.1 reading_writing_json_1/reading_writing_json_1.go
10 type helloWorldResponse struct {
11 Message string
12 }
In our handler, we will create an instance of this object, set the message, then use the Marshal function to encode it to a string before returning.
Let's see what that will look like:
23 func helloWorldHandler(w http.ResponseWriter, r *http.Request) {
24 response := helloWorldResponse{Message: "HelloWorld"}
25 data, err := json.Marshal(response)
26 if err != nil {
27 panic("Ooops")
28 }
29
30 fmt.Fprint(w, string(data))
31 }
Now, when we run our program again and refresh our browser, we see the following output rendered in valid JSON:
{"Message":"Hello World"}
This is awesome; however, the default behavior of Marshal is to take the literal name of the field and use this as the field in the JSON output. What if I prefer to use camel case and would rather see "message", could we just rename the field in the helloWorldResponse struct?
Unfortunately we can't, as in Go, lowercase properties are not exported, Marshal will ignore these and will not include them in the output.
All is not lost as the encoding/json package implements struct field attributes that allow us to change the output for the property to anything we choose.
Example 1.2 reading_writing_json_2/reading_writing_json_2.go
10 type helloWorldResponse struct {
11 Message string `json:"message"`
12 }
Using the struct field's tags, we can have greater control of how the output will look. In the preceding example, when we marshal this struct the output from our server would be:
{"message":"Hello World"}
This is exactly what we want, but we can use field tags to control the output even further. We can convert object types and even ignore a field altogether if we need to:
type helloWorldResponse struct {
// change the output field to be "message"
Message string `json:"message"`
// do not output this field
Author string `json:"-"`
// do not output the field if the value is empty
Date string `json:",omitempty"`
// convert output to a string and rename "id"
Id int `json:"id, string"`
}
Channel, complex types, and functions cannot be encoded in JSON; attempting to encode these types will result in an UnsupportedTypeError being returned by the Marshal function.
It also can't represent cyclic data structures; if your stuct contains a circular reference then Marshal will result in an infinite recursion, which is never a good thing for a web request.
If we want to export our JSON prettily formatted with indentation, we can use the MarshallIndent function, this allows you to pass an additional parameter of string to specify what you would like the indent to be. Two spaces right, not a tab?
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
The astute reader might have noticed that we are decoding our struct into a byte array and then writing that to the response stream, this does not seem to be particularly efficient and in fact it is not. Go provides Encoders and Decoders, which can write directly to a stream, since we already have a stream with the ResponseWriter then let's do just that.
Before we do, I think we need to look at the ResponseWriter a little to see what is going on there.
The ResponseWriter is an interface that defines three methods:
// Returns the map of headers which will be sent by the
// WriteHeader method.
Header()
// Writes the data to the connection. If WriteHeader has not
// already been called then Write will call
// WriteHeader(http.StatusOK).
Write([]byte) (int, error)
// Sends an HTTP response header with the status code.
WriteHeader(int)
If we have a ResponseWriter interface, how can we use this with fmt.Fprint(w io.Writer, a ...interface{})? This method requires a Writer interface as a parameter and we have a ResponseWriter interface. If we look at the signature for Writer we can see that it is:
Write(p []byte) (n int, err error)
Because the ResponseWriter interface implements this method, it also satisfies the interface Writer and therefore any object that implements ResponseWriter can be passed to any function that expects Writer.
Amazing, Go rocks, but we have not answered our question, Is there any better way to send our data to the output stream without marshalling to a temporary object before we return it?
The encoding/json package has a function called NewEncoder this returns us an Encoder object that can be used to write JSON straight to an open writer and guess what; we have one of those:
func NewEncoder(w io.Writer) *Encoder
So instead of storing the output of Marshal into a byte array, we can write it straight to the HTTP response.
Example 1.3 reading_writing_json_3/reading_writing_json_3.go:
func helloWorldHandler(w http.ResponseWriter, r *http.Request) {
response := HelloWorldResponse{Message: "HelloWorld"}
encoder := json.NewEncoder(w)
encoder.Encode(&response)
}
We will look at benchmarking in a later chapter, but to see why this is important we have created a simple benchmark to check the two methods against each other, have a look at the output.
Example 1.4 reading_writing_json_2/reading_writing_json_2.go:
$go test -v -run="none" -bench=. -benchtime="5s" -benchmem
BenchmarkHelloHandlerVariable-8 20000000 511 ns/op 248 B/op 5 allocs/op
BenchmarkHelloHandlerEncoder-8 20000000 328 ns/op 24 B/op 2 allocs/op
BenchmarkHelloHandlerEncoderReference-8 20000000 304 ns/op 8 B/op 1 allocs/op
PASS
ok github.com/building-microservices-with-go/chapter1/reading_writing_json_2 24.109s
Using Encoder rather than marshalling to a byte array is nearly 50% faster. We are dealing with nanoseconds here so that time may seem irrelevant, but it isn't; this was two lines of code. If you have that level of inefficiency throughout the rest of your code then your application will run slower, you will need more hardware to satisfy the load and that will cost you money. There is nothing clever in the differences between the two methods all we have done is understood how the standard packages work and chosen the correct option for our requirements, that is not performance tuning, that is understanding the framework.