Golang处理JSON(一)--- 编码

JSON

http的交互的生命周期包含请求和响应。前面我们介绍了很多关于发起请求,处理请求的内容。现在该聊一聊返回响应内容了。对于web服务的响应,以前常见的响应是返回服务端渲染的模板。浏览器只要展示模板即可。随着Restful风格的api出现,已经前后端分离,更多的返回格式是json字串。本节我们将讨论在golang中如何编码和解码json。

JSON是一种数据格式描述语言。以key和value构成的哈系结构,类似javascript中的对象,python中的字典。通常json格式的key是字串,其值可以是任意类型,字串,数字,数组或者对象结构。更多关于json的可以访问JSON了解。

数据结构map

json源于javascript的对象结构,golang中直接对应的数据结构,可是golang的map也是key-value结构,同时struct结构体也可以描述json。当然,对于json的数据类型,go也会有对象的结构所匹配。大致对应关系如下:

数据类型 JSON Golang
字串 string string
整数 number int64
浮点数 number flaot64
数组 arrary slice
对象 object struct
布尔 bool bool
空值 null nil

基本结构编码

golang提供了encoding/json的标准库用于编码json。大致需要两步:

  1. 首先定义json结构体。
  2. 使用 Marshal方法序列化。

定义结构体的时候,只有字段名是大写的,才会被编码到json当中。

type Account struct {
    Email string
    password string
    Money float64
}

func main() {
    account := Account{
        Email: "rsj217@gmail.com",
        password: "123456",
        Money: 100.5,
    }

    rs, err := json.Marshal(account)
    if err != nil{
        log.Fatalln(err)
    }

    fmt.Println(rs)
    fmt.Println(string(rs))
}

可以看到输出如下,Marshal方法接受一个空接口的参数,返回一个[]byte结构。小写命名的password字段没有被编码到json当中,生成的json结构字段和Account结构一致。

[123 34 69 109 97 105 108 34 58 34 114 115 106 50 49 55 64 103 109 97 105 108 46 99 111 109 34 44 34 77 111 110 101 121 34 58 49 48 48 46 53 125]
{"Email":"rsj217@gmail.com","Money":100.5}

复合结构编码

相比字串,数字等基本数据结构,slice切片,map图则是复合结构。这些结构编码也类似。不过map的key必须是字串,而value必须是同一类型的数据。

type User struct {
    Name    string
    Age     int
    Roles   []string
    Skill   map[string]float64
}

func main() {

    skill := make(map[string]float64)

    skill["python"] = 99.5
    skill["elixir"] = 90
    skill["ruby"] = 80.0

    user := User{
        Name:"rsj217",
        Age: 27,
        Roles: []string{"Owner", "Master"},
        Skill: skill,
    }

    rs, err := json.Marshal(user)
    if err != nil{
        log.Fatalln(err)
    }
    fmt.Println(string(rs))
}

输入:

{
    "Name":"rsj217",
    "Age":27,
    "Roles":[
        "Owner",
        "Master"
    ],
    "Skill":{
        "elixir":90,
        "python":99.5,
        "ruby":80
    }
}

嵌套编码

slice和map可以匹配json的数组和对象,当前提是对象的value是同类型的情况。更通用的做法,对象的key可以是string,但是其值可以是多种结构。golang可以通过定义结构体实现这种构造:

type User struct {
    Name    string
    Age     int
    Roles   []string
    Skill   map[string]float64
    Account Account
}

func main(){
    
    ...

    user := User{
        Name:"rsj217",
        Age: 27,
        Roles: []string{"Owner", "Master"},
        Skill: skill,
        Account:account,
    }
    ...
}

输出:

{
    "Name":"rsj217",
    "Age":27,
    "Roles":[
        "Owner",
        "Master"
    ],
    "Skill":{
        "elixir":90,
        "python":99.5,
        "ruby":80
    },
    "Account":{
        "Email":"rsj217@gmail.com",
        "Money":100.5
    }
}

通过定义嵌套的结构体Account,实现了key与value不一样的结构。golang的数组或切片,其类型也是一样的,如果遇到不同数据类型的数组,则需要借助空结构来实现:

type User struct {
    ...

    Extra []interface{}
}

extra := []interface{}{123, "hello world"}

user := User{
    ...
    
    Extra:   extra,
}

输出:

{
    ...
    "Extra":[
        123,
        "hello world"
    ]
}

使用空接口,也可以定义像结构体实现那种不同value类型的字典结构。当空接口没有初始化其值的时候,零值是 nil。编码成json就是 null

type User struct {
    Name    string
    Age     int
    Roles   []string
    Skill   map[string]float64
    Account Account

    Extra []interface{}

    Level map[string]interface{}
}

func main() {

    ...

    level := make(map[string]interface{})

    level["web"] = "Good"
    level["server"] = 90
    level["tool"] = nil

    user := User{
        Name:    "rsj217",
        Age:     27,
        Roles:   []string{"Owner", "Master"},
        Skill:   skill,
        Account: account,
        Level:   level,
    }

    ...
}

输出:

{
    ...
    "Extra":null,
    "Level":{
        "server":90,
        "tool":null,
        "web":"Good"
    }
}

可以看到 Extra返回的并不是一个空的切片,而是null。同时Level字段实现了向字典的嵌套结构。

StructTag 字段重名

通过上面的例子,我们看到了Level字段中的keyserver等是小写字母,其他的都是大写字母。因为我们在定义结构的时候,只有使用大写字母开头的字段才会被导出。而通常json世界中,更盛行小写字母的方式。看起来就成了一个矛盾。其实不然,golang提供了struct tag的方式可以重命名结构字段的输出形式。

type Account struct {
    Email    string  `json:"email"`
    Password string  `json:"pass_word"`
    Money    float64 `json:"money"`
}

func main() {
    account := Account{
        Email:    "rsj217@gmail.com",
        Password: "123456",
        Money:    100.5,
    }

    rs, err := json.Marshal(account)
    ...
}

我们使用struct tag,重新给Aaccount结构的字段进行了重命名。其中email小写了,并且password字段还使用了下划线,输出的结果如下:

{"email":"rsj217@gmail.com","pass_word":"123456","money":100.5}
-忽略字段

重命名的可以一个利器,这个利器还提供了更高级的选项。通常使用marshal的时候,会把结构体的所有除了私有字段都编码到json,而实际开发中,我们定义的结构可能更通用,我们需要某个字段可以导出,但是又不能编码到json中。

此时使用 struact tag的 -符号就能完美解决,我们已经知道_常用于忽略字段的占位,在tag中则使用短横线-

type Account struct {
    Email    string  `json:"email"`
    Password string  `json:"password,omitempty"`
    Money    float64 `json:"money"`
}

输出:

{"email":"rsj217@gmail.com","money":100.5}

可见即使Password不是私有字段,因为-忽略了它,因此没有被编码到json输出。

omitempty可选字段

对于另外一种字段,当其有值的时候就输出,而没有值(零值)的时候就不输出,则可以使用另外一种选项omitempty

type Account struct {
    Email    string  `json:"email"`
    Password string  `json:"password,omitempty"`
    Money    float64 `json:"money"`
}
func main() {
    account := Account{
        Email:    "rsj217@gmail.com",
        Password: "",
        Money:    100.5,
    }
    ...
}

此时password不会被编码到json输出中。

string选项

gplang是静态类型语言,对于类型定义的是不能动态修改。在json处理当中,struct tag的string可以起到部分动态类型的效果。有时候输出的json希望是数字的字符串,而定义的字段是数字类型,那么就可以使用string选项。

type Account struct {
    Email    string  `json:"email"`
    Password string  `json:"password,omitempty"`
    Money    float64 `json:"money,string"`
}
func main() {
    account := Account{
        Email:    "rsj217@gmail.com",
        Password: "123",
        Money:    100.50,
    }

    ...
}

可以看到输出为 money: "100.5", money字段的值是字串。(其实能转换成100.50会比转换成100.5更好,可是我没有找到通过tag的方式实现 :()。

总结

上面所介绍的大致覆盖了golang的json编码处理。总体原则分两步,首先定义需要编码的结构,然后调用encoding/json标准库的Marshal方法生成json byte数组,转换成string类型即可。

golang和json的大部分数据结构匹配,对于复合结构,go可以借助结构体和空接口实现json的数组和对象结构。通过struct tag可以灵活的修改json编码的字段名和输出控制。

既然有JSON的编码,当然就会有JSON的解码。相比编码JSON,解析JSON对于golang则需要更多的技巧。下一节我们聊一聊golang解析json相关的内容。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 143,201评论 1 300
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 61,387评论 1 257
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 94,683评论 0 213
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 41,080评论 0 179
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 48,865评论 1 255
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 38,810评论 1 177
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,440评论 2 271
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,200评论 0 167
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 29,019评论 6 231
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 32,540评论 0 213
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,337评论 2 215
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 30,658评论 1 231
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 24,255评论 0 32
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,145评论 2 214
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 31,535评论 3 206
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,626评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,028评论 0 166
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 33,555评论 2 230
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 33,652评论 2 231

推荐阅读更多精彩内容