Golang编解码JSON与HTTP请求
本文基于实际工作加之诸多前辈的博文结晶拼合而来,由以下部分组成:
- 概念
- 解码
2.1 解析到结构体
2.2 解析到interface
2.3 SimpleJSON - 编码
- json.NewEncoder与json.NewDecoder
- HTTP请求
文里的例子可以直接在the go playground里跑,已测试。https://play.golang.org/
概念
JSON语法
先来明确一下JSON语法:
- JSON语法是JavaScript语法的子集。JSON语法是JavaScript对象表示法语法的子集。
- 数据在名称/值对中
- 数据由逗号分隔
- 大括号保存对象
- 中括号保存数组
-
JSON data is written as name/value pairs.
A name/value pair consists of a field name (in double quotes), followed by a colon, followed by a value:
"name":"John"
-
In JSON, values must be one of the following data types:
- a string(在双引号中。在JS里可以单括号,但JSON需是双括号)
- a number(整数或浮点数)
- an object (JSON object,在大括号中)
- an array(在中括号中)
- a boolean(True或False)
- null
JSON对象:
- JSON 对象在大括号({})中书写
- 对象可以包含多个名称/值对
{
"name": "runoob",
"alexa": 10000,
"sites": {
"site1": "www.runoob.com",
"site2": "m.runoob.com",
"site3": "c.runoob.com"
}
}
JSON数组:
- JSON 数组在中括号中书写
- 数组可包含多个对象
{
"sites": [
{
"name": "菜鸟教程",
"url": "www.runoob.com"
},
{
"name": "google",
"url": "www.google.com"
},
{
"name": "微博",
"url": "www.weibo.com"
}
]
}
序列化与反序列化
序列化相关也可移步至:https://sowhatbigfatloser.com/xu-lie-hua-yu-fan-xu-lie-hua/
序列化:就是将对象转化成二进制序列的过程。
反序列化:就是讲二进制序列转化成对象的过程。
我们把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。
序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。
反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。
解码
实际工作中运用到,解析返回JSON目前看来有三种方式:
- 解析到结构体:如果知道被解析的JSON的结构
- 解析到interface:用
map[string]interface{}
先接过来 - SimpleJSON:单纯数据解析,可使用第三方库
解析到结构体
Go的JSON包中有如下函数:
func Unmarshal(data []byte, v interface{}) error
比较直接的情况, 注意需要unmarshal到&变量地址,而不是变量:
package main
import (
"encoding/json"
"fmt"
)
func main() {
resp := `{ "code": "00",
"message": "SUCCESS",
"describe": "成功",
"resultInfo": { "uniqueNumber": "20180816" }
}`
//定义结构体
type JsonResp struct {
Code int `json:"code"`
Message string `json:"message"`
Describe string `json:"describe"`
ResultInfo map[string]string `json:"resultInfo"`
}
var smsresp JsonResp
temp := []byte(resp)
//注意需要unmarshal到&smsresp,而不是smsresp
json.Unmarshal(temp, &smsresp)
fmt.Println(smsresp.Code)
fmt.Println(smsresp.Describe)
fmt.Println(smsresp.Message)
fmt.Println(smsresp.ResultInfo["uniqueNumber"])
}
复杂一些的结构,可以尝试在JSON中嵌套JSON:
package main
import (
"encoding/json"
"fmt"
"time"
)
func main() {
resp := `{ "code": "00",
"message": "SUCCESS",
"describe": "成功",
"resultInfo": [
{"uniqueNumber": 20180816,
"created_at": "2019-05-24T11:33:18+08:00",
"updated_at": "2021-03-01T23:40:20+08:00",
"last_updated_by": "service_centerA"
},
{"uniqueNumber": 20180817,
"created_at": "2019-05-24T11:33:19+08:00",
"updated_at": "2021-03-01T23:40:21+08:00",
"last_updated_by": "service_centerB"
},
{"uniqueNumber": 20180818,
"created_at": "2019-05-24T11:33:20+08:00",
"updated_at": "2021-03-01T23:40:22+08:00",
"last_updated_by": "service_centerC"
}
]
}`
//定义结构体, 里面是json.RawMessage
type JsonResp struct {
Code int `json:"code"`
Message string `json:"message"`
Describe string `json:"describe"`
ResultInfo json.RawMessage `json:"resultInfo"`
}
//定义内部的结构体
type Result struct {
Uniquenumber int `json:"uniqueNumber"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
LastUpdatedBy string `json:"last_updated_by"`
}
//一度unmarshal拿到response
var smsresp JsonResp
temp := []byte(resp)
json.Unmarshal(temp, &smsresp)
//二度unmarshal拿到result
var smsresults []Result
json.Unmarshal(smsresp.ResultInfo, &smsresults)
fmt.Println("========Response========")
fmt.Println(smsresp.Code)
fmt.Println(smsresp.Describe)
fmt.Println(smsresp.Message)
fmt.Println("========Result========")
fmt.Println(smsresults[1].Uniquenumber)
fmt.Println(smsresults[1].CreatedAt)
fmt.Println(smsresults[1].LastUpdatedBy)
}
输出结果:
========Response========
0
成功
SUCCESS
========Result========
20180817
2019-05-24 11:33:19 +0800 +0800
service_centerB
注意: 能够被赋值的字段必须是可导出字段,即首字母大写。 同时JSON解析的时候只会解析能找得到的字段,找不到的字段会被忽略。我们在实际使用的过程中一定要随时警惕这一点。
其实与这个潜在的坑相比,它的优势非常明显:当你接收到一个很大的JSON数据结构而你却只想获取其中的部分数据的时候,你只需将你想要的数据对应的字段名大写,即可轻松解决。
解析到interface
我们知道interface{}
可以用来存储任意数据类型的对象,这种数据结构正好用于存储解析的未知结构的json数据的结果。JSON包中采用map[string]interface{}
和[]interface{}
结构来存储任意的JSON对象和数组。Go类型和JSON类型的对应关系如下:
类型 | JSON类型 |
---|---|
bool | JSON booleans |
float64 | JSON numbers |
string | JSON strings |
nil | JSON null |
现在我们假设有如下的JSON数据
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
如果在我们不知道他的结构的情况下,我们把他解析到interface{}里面
var f interface{}
err := json.Unmarshal(b, &f)
这个时候f里面存储了一个map类型,他们的key是string,值存储在空的interface{}里
f = map[string]interface{}{
"Name": "Wednesday",
"Age": 6,
"Parents": []interface{}{
"Gomez",
"Morticia",
},
}
那么如何来访问这些数据呢?通过断言的方式:
m := f.(map[string]interface{})
通过断言之后,你就可以通过如下方式来访问里面的数据了
for k, v := range m {
switch vv := v.(type) {
case string:
fmt.Println(k, "is string", vv)
case int:
fmt.Println(k, "is int", vv)
case float64:
fmt.Println(k,"is float64",vv)
case []interface{}:
fmt.Println(k, "is an array:")
for i, u := range vv {
fmt.Println(i, u)
}
default:
fmt.Println(k, "is of a type I don't know how to handle")
}
}
综合实践的程序如下:
package main
import (
"fmt"
"encoding/json"
)
func main() {
resp := `{"code": "00",
"message": "SUCCESS",
"describe": "成功",
"resultInfo": {"uniqueNumber": "201808161"}
}`
var x interface{}
_ = json.Unmarshal([]byte(resp), &x)
fmt.Println("x contains: ", x)
fmt.Println("===============")
m := x.(map[string]interface{})
for k, v := range m {
switch vv := v.(type) {
case string:
fmt.Println(k, "is string", vv)
case int:
fmt.Println(k, "is int", vv)
case float64:
fmt.Println(k, "is float64", vv)
case []interface{}:
fmt.Println(k, "is an array:")
for i, u := range vv {
fmt.Println(i, u)
}
case map[string]interface{}:
fmt.Println(k, "is an map[string]string:")
for i, u := range vv {
fmt.Println(i, u)
}
default:
fmt.Println(k, "is of a type didn't handle")
}
}
}
输出结果:
x contains: map[code:00 describe:成功 message:SUCCESS resultInfo:map[uniqueNumber:201808161]]
===============
code is string 00
message is string SUCCESS
describe is string 成功
resultInfo is an map[string]string:
uniqueNumber 201808161
SimpleJSON
单纯用第三方库github.com/bitly/go-simplejson
解析
package main
import (
"fmt"
"github.com/bitly/go-simplejson"
)
func main() {
resp := `{"code": "00",
"message": "SUCCESS",
"describe": "成功",
"resultInfo": { "uniqueNumber": "20180816" }
}`
js, errs := simplejson.NewJson([]byte(resp))
if errs != nil {
return
}
discount := js.Get("resultInfo").Get("uniqueNumber")
strcode, _ := js.Get("code").String()
intcode, _ := js.Get("code").Int()
path := js.GetPath("resultInfo", "uniqueNumber")
fmt.Println(discount)
fmt.Println(strcode)
fmt.Println(intcode)
fmt.Println(path)
}
输出结果:
&{201808161133401673324075025000035}
00
0
&{201808161133401673324075025000035}
编码
Marshal()和MarshalIndent()函数可以将数据封装成json数据。
- struct、slice、array、map都可以转换成json
- struct转换成json的时候,只有字段首字母大写的才会被转换
- map转换的时候,key必须为string
- 封装的时候,如果是指针,会追踪指针指向的对象进行封装
先有数据结构,然后Marshal。JSON包里Marshal函数来处理,函数定义如下:
func Marshal(v interface{}) ([]byte, error)
例子:
package main
import (
"encoding/json"
"fmt"
)
type Server struct {
ServerName string
ServerIP string
}
type Serverslice struct {
Servers []Server
}
func main() {
var s Serverslice
s.Servers = append(s.Servers, Server{ServerName: "Shanghai_VPN", ServerIP: "127.0.0.1"})
s.Servers = append(s.Servers, Server{ServerName: "Beijing_VPN", ServerIP: "127.0.0.2"})
b, err := json.Marshal(s)
if err != nil {
fmt.Println("json err:", err)
}
fmt.Println(string(b))
}
输出如下内容:
{"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]}
我们看到上面的输出字段名的首字母都是大写的,如果你想用小写的首字母怎么办呢?把结构体的字段名改成首字母小写的?JSON输出的时候必须注意,只有导出的字段才会被输出,如果修改字段名,那么就会发现什么都不会输出,所以必须通过struct tag定义来实现:
type Server struct {
ServerName string `json:"serverName"`
ServerIP string `json:"serverIP"`
}
type Serverslice struct {
Servers []Server `json:"servers"`
}
通过修改上面的结构体定义,输出的JSON串就和我们最开始定义的JSON串保持一致了。
针对JSON的输出,我们在定义struct tag的时候需要注意的几点是:
- 字段的tag是"-",那么这个字段不会输出到JSON
- tag中带有自定义名称,那么这个自定义名称会出现在JSON的字段名中,例如上面例子中serverName
- tag中如果带有"omitempty"选项,那么如果该字段值为空,就不会输出到JSON串中
- 如果字段类型是bool, string, int, int64等,而tag中带有",string"选项,那么这个字段在输出到JSON的时候会把该字段对应的值转换成JSON字符串
举例来说:
package main
import (
"encoding/json"
"os"
)
type Server struct {
// ID 不会导出到JSON中
ID int `json:"-"`
// ServerName2 的值会进行二次JSON编码
ServerName string `json:"serverName"`
ServerName2 string `json:"serverName2,string"`
// 如果 ServerIP 为空,则不输出到JSON串中
ServerIP string `json:"serverIP,omitempty"`
}
func main() {
s := Server {
ID: 3,
ServerName: `Go "1.0" `,
ServerName2: `Go "1.0" `,
ServerIP: ``,
}
b, _ := json.Marshal(s)
os.Stdout.Write(b)
}
输出结果:
{"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.0\\\" \""}
Marshal函数只有在转换成功的时候才会返回数据,在转换的过程中我们需要注意几点:
- JSON对象只支持string作为key,所以要编码一个map,那么必须是map[string]T这种类型(T是Go语言中任意的类型)
- Channel, complex和function是不能被编码成JSON的
- 嵌套的数据是不能编码的,不然会让JSON编码进入死循环
- 指针在编码的时候会输出指针指向的内容,而空指针会输出null
json.NewEncoder与json.NewDecoder
除了marshal和unmarshal函数,Go还提供了Decoder和Encoder对streamJSON进行处理,常见 request中的Body、文件等。
编码
json.NewEncoder(<Writer>).encode(v)
json.Marshal(&v)
解码
json.NewDecoder(<Reader>).decode(&v)
json.Unmarshal([]byte, &v)
例子:
$ cat post.json
{
"name":"apple 8P",
"product_id":10,
"number":10000,
"price":6000,
"is_on_sale":"true"
}
$ cat json9.go
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
type Product struct {
Name string
ProductID int64
Number int
Price float64
IsOnSale bool
}
func main() {
jsonFile, err := os.Open("post.json")
if err != nil {
fmt.Println("Error opening json file:", err)
return
}
defer jsonFile.Close()
decoder := json.NewDecoder(jsonFile)
for {
var post Product
err := decoder.Decode(&post)
if err == io.EOF {
break
}
if err != nil {
fmt.Println("error decoding json:", err)
return
}
fmt.Println(post)
}
}
运行结果:
$ go run json9.go
{apple 8P 0 10000 6000 false}
两种编解码比较:
package main
import (
"encoding/json"
"fmt"
"bytes"
"strings"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
// 1. 使用 json.Marshal 编码
person1 := Person{"张三", 24}
bytes1, err := json.Marshal(&person1)
if err == nil {
// 返回的是字节数组 []byte
fmt.Println("json.Marshal 编码结果: ", string(bytes1))
}
// 2. 使用 json.Unmarshal 解码
str := `{"name":"李四","age":25}`
// json.Unmarshal 需要字节数组参数, 需要把字符串转为 []byte 类型
bytes2 := []byte(str) // 字符串转换为字节数组
var person2 Person // 用来接收解码后的结果
if json.Unmarshal(bytes2, &person2) == nil {
fmt.Println("json.Unmarshal 解码结果: ", person2.Name, person2.Age)
}
// 3. 使用 json.NewEncoder 编码
person3 := Person{"王五", 30}
// 编码结果暂存到 buffer
bytes3 := new(bytes.Buffer)
_ = json.NewEncoder(bytes3).Encode(person3)
if err == nil {
fmt.Print("json.NewEncoder 编码结果: ", string(bytes3.Bytes()))
}
// 4. 使用 json.NewDecoder 解码
str4 := `{"name":"赵六","age":28}`
var person4 Person
// 创建一个 string reader 作为参数
err = json.NewDecoder(strings.NewReader(str4)).Decode(&person4)
if err == nil {
fmt.Println("json.NewDecoder 解码结果: ", person4.Name, person4.Age)
}
}
运行结果:
json.Marshal 编码结果: {"name":"张三","age":24}
json.Unmarshal 解码结果: 李四 25
json.NewEncoder 编码结果: {"name":"王五","age":30}
json.NewDecoder 解码结果: 赵六 28
HTTP请求
发送GET
//基本的GET请求可以直接使用http.Get方法
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
resp, err := http.Get("http://httpbin.org/get")
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
fmt.Println(resp.StatusCode)
if resp.StatusCode == 200 {
fmt.Println("ok")
}
}
发送JSON数据的post请求
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
client := &http.Client{}
data := make(map[string]interface{})
data["name"] = "zhaofan"
data["age"] = "23"
bytesData, _ := json.Marshal(data)
req, _ := http.NewRequest("POST","http://httpbin.org/post",bytes.NewReader(bytesData))
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
其他Golang语言中的著名Json库
- encoding/json
- jsoniter
- jsonparser
- fastjson
- easyjson
- GJSON
References:
https://tech.meituan.com/2015/02/26/serialization-vs-deserialization.html
https://golang.org/pkg/encoding/json/
https://studygolang.com/articles/18462
https://www.cnblogs.com/Detector/p/9048678.html
https://segmentfault.com/q/1010000008997626
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/07.2.md
https://www.cnblogs.com/f-ck-need-u/p/10080793.html
https://www.cnblogs.com/zhaof/p/11346412.html
https://zhuanlan.zhihu.com/p/80442700
https://sanyuesha.com/2018/05/07/go-json/
https://blog.csdn.net/weixin_33795743/article/details/87958551?utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.baidujs&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.baidujs
https://my.oschina.net/u/4360480/blog/4521023
https://www.liaoxuefeng.com/wiki/1016959663602400/1017624706151424