- Building Microservices with Go
- Nic Jackson
- 1048字
- 2021-07-15 17:28:02
Unmarshalling JSON to Go structs
Now we have learned how we can send JSON back to the client, what if we need to read input before returning the output? We could use URL parameters and we will see what that is all about in the next chapter, but commonly you will need more complex data structures that involve the service to accept JSON as part of an HTTP POST request.
Applying similar techniques that we learned in the previous section to write JSON, reading JSON is just as easy. To decode JSON into a stuct field the encoding/json package provides us with the Unmarshal function:
func Unmarshal(data []byte, v interface{}) error
The Unmarshal function works in the opposite way to Marshal; it allocates maps, slices, and pointers as required. Incoming object keys are matched using either the struct field name or its tag and will work with a case-insensitive match; however, an exact match is preferred. Like Marshal, Unmarshal will only set exported struct fields, those that start with an upper-case letter.
We start by adding a new struct field to represent the request, whilst Unmarshal can decode the JSON into an interface{}, which would be of map[string]interface{} // for JSON objects type or: []interface{} // for JSON arrays, depending if our JSON is an object or an array.
In my opinion it is much clearer to the readers of our code if we explicitly state what we are expecting as a request. We can also save ourselves work by not having to manually cast the data when we come to use it.
Remember two things:
- You do not write code for the compiler, you write code for humans to understand
- You will spend more time reading code than you do writing it
Taking these two points into account we create a simple struct to represent our request, which will look like this:
Example 1.5 reading_writing_json_4/reading_writing_json_4.go:
14 type helloWorldRequest struct {
15 Name string `json:"name"`
16 }
Again, we are going to use struct field tags as whilst we could let Unmarshal do case-insensitive matching so {"name": "World} would correctly unmarshal into the struct the same as {"Name": "World"}, when we specify a tag we are being explicit about the request form and that is a good thing. In terms of speed and performance it is also about 10% faster, and remember, performance matters.
To access the JSON sent with the request we need to take a look at the http.Request object passed to our handler. The following listing does not show all the methods on the request, just the ones we are going to be immediately dealing with, for full documentation I recommend checking out the documentation at https://godoc.org/net/http#Request:
type Requests struct {
...
// Method specifies the HTTP method (GET, POST, PUT, etc.).
Method string
// Header contains the request header fields received by the server. The type Header is a link to map[string] []string.
Header Header
// Body is the request's body.
Body io.ReadCloser
...
}
The JSON that has been sent with the request is accessible in the Body field. Body implements the interface io.ReadCloser as a stream and does not return a []byte or a string. If we need the data contained in the body, we can simply read it into a byte array, as shown in the following example:
30 body, err := ioutil.ReadAll(r.Body)
31 if err != nil {
32 http.Error(w, "Bad request", http.StatusBadRequest)
33 return
34 }
Here is something we'll need to remember. We are not calling Body.Close(), if we were making a call with a client we would need to do this as it is not automatically closed, however, when used in a ServeHTTP handler, the server automatically closes the request stream.
To see how this all works inside our handler, we can look at the following handler:
28 func helloWorldHandler(w http.ResponseWriter, r *http.Request) {
29
30 body, err := ioutil.ReadAll(r.Body)
31 if err != nil {
32 http.Error(w, "Bad request", http.StatusBadRequest)
33 return
34 }
35
36 var request helloWorldRequest
37 err = json.Unmarshal(body, &request)
38 if err != nil {
39 http.Error(w, "Bad request", http.StatusBadRequest)
40 return
41 }
42
43 response := helloWorldResponse{Message: "Hello " + request.Name}
44
45 encoder := json.NewEncoder(w)
46 encoder.Encode(response)
47 }
Let's run this example and see how it works. To test, we can simply use the curl command to send a request to the running server. If you feel more comfortable using a GUI tool than Postman (which is available for the Google Chrome browser), they will work just fine or feel free to use your preferred tool:
$ curl localhost:8080/helloworld -d '{"name":"Nic"}'
You should see the following response:
{"message":"Hello Nic"}
What do you think will happen if you do not include a body with your request?
$ curl localhost:8080/helloworld
If you guessed correctly, that you would get a HTTP status 400 Bad Request, then you win a prize:
func Error(w ResponseWriter, error string, code int)
Errors reply to the request with the given message and status code. Once we have sent this, we need to return stopping further execution of the function as this does not close the ResponseWriter interface and return flow to the calling function automatically.
Just before you think you are done, have a go and see if you can improve the performance of the handler. Think about the things we were talking about when marshaling JSON.
Got it?
Well if not here is the answer, again all we are doing is using the Decoder, which is the opposite of the Encoder that we used in writing JSON. It has an instant 33% performance increase and less code too.
Example 1.6 reading_writing_json_5/reading_writing_json_5.go:
27 func helloWorldHandler(w http.ResponseWriter, r *http.Request) {
28
29 var request HelloWorldRequest
30 decoder := json.NewDecoder(r.Body)
31
32 err := decoder.Decode(&request)
33 if err != nil {
34 http.Error(w, "Bad request", http.StatusBadRequest)
35 return
36 }
37
38 response := HelloWorldResponse{Message: "Hello " + request.Name}
39
40 encoder := json.NewEncoder(w)
41 encoder.Encode(response)
42 }
Now we can see just how easy it is to encode and decode JSON with Go, I would recommend taking five minutes now to spend some time digging through the documentation for the encoding/json package (https://golang.org/pkg/encoding/json/) as there is a whole lot more that you can do with this.
- FFmpeg入門詳解:音視頻流媒體播放器原理及應用
- 深入淺出Android Jetpack
- 實戰Java高并發程序設計(第3版)
- C語言程序設計
- Webpack實戰:入門、進階與調優
- Vue.js 2 Web Development Projects
- 響應式Web設計:HTML5和CSS3實戰(第2版)
- Scratch趣味編程:陪孩子像搭積木一樣學編程
- Azure Serverless Computing Cookbook
- Nagios Core Administration Cookbook(Second Edition)
- 測試架構師修煉之道:從測試工程師到測試架構師
- Instant Apache Camel Messaging System
- Advanced Python Programming
- C語言從入門到精通
- Spark技術內幕:深入解析Spark內核架構設計與實現原理