diff --git a/README.md b/README.md index 1839e1f..fb28aa0 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,24 @@ Enveloping response data like this isn't strictly necessary, and whether you cho 2. It reduces the risk of errors on the client side, because it's harder to accidentally process one response thinking that it is something different. To get at the data, a client must explicitly reference it via the "movie" key. 3. If we always envelope the data returned by our API, then we mitigate a security vulnerability in older browsers which can arise if you return a JSON array as a response. +### Advanced JSON Customization + +_When Go is encoding a particular type to JSON, it looks to see if the type has a **MarshalJSON()** method implemented on it. If it has, then Go will call this method to determine how to encode it._ + +Strictly speaking, when Go is encoding a particular type to JSON it looks to see if the type satisfies the json.Marshaler interface, which looks like this : + +`type Marshaler interface { + MarshalJSON() ([]byte, error) +}` + +If the type does satisfy the interface, then Go will call its **MarshalJSON()** method and use the []byte slice that it returns as the encoded JSON value. + +If the type doesn't have a **MarshalJSON()** method, then Go will fall back to trying to encode it to JSON based on its own internal set of rules. + +So, if we want to customize how something is encoded, all we need to do is implement a **MarshalJSON()** method on it which returns a _custom JSON representation of itself_ in a **[]byte** slice. + +An example is available here : **internal/data/runtime.go** + ### Performance json.MarshalIndent() takes 65% longer to run and uses around 30% more memory than json.Marshal(), as well as making two more heap allocations. Those figures will change depending on what you're encoding, but they're fairly indicative of the performance impact. diff --git a/internal/data/runtime.go b/internal/data/runtime.go new file mode 100644 index 0000000..689a277 --- /dev/null +++ b/internal/data/runtime.go @@ -0,0 +1,33 @@ +package data + +import ( + "fmt" + "strconv" +) + +// Runtime +/* +Declare a custom Runtime type, which has the underlying type int32 +(the same as our Movie struct field) +*/ +type Runtime int32 + +// MarshalJSON +/* +Implement a MarshalJSON() method on the Runtime type so that it satisfies +the json.Marshaler interface. This should return the JSON-encoded value +for the movie runtime (in our case, it will return string in the format +" mins"). +*/ +func (r Runtime) MarshalJSON() ([]byte, error) { + // Generate a string containing the movie runtime in the required format. + jsonValue := fmt.Sprintf("%d mins", r) + + // Use the strconv.Quote() function on the string to wrap it in double + // quotes. It needs to be surrounded by double quotes in order to be + // a valid *JSON string*. + quotedJSONValue := strconv.Quote(jsonValue) + + // Convert the quoted string value to a byte slice and return it. + return []byte(quotedJSONValue), nil +}