官术网_书友最值得收藏!

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.

主站蜘蛛池模板: 永春县| 锦屏县| 界首市| 威信县| 北安市| 依兰县| 大同县| 北票市| 准格尔旗| 崇礼县| 龙江县| 兴仁县| 喀什市| 拉萨市| 连城县| 海淀区| 罗田县| 镇平县| 丹凤县| 陇南市| 宁明县| 宁河县| 华亭县| 建湖县| 佳木斯市| 蓝山县| 河北区| 赣榆县| 成都市| 万宁市| 普格县| 通渭县| 黔西县| 巴青县| 安徽省| 桓台县| 安阳县| 南汇区| 卢湾区| 永宁县| 天等县|