golang json 获取所有key_Golang —— JSON 大法

Golang处理JSON:获取所有键与自定义转换
本文介绍了Golang中处理JSON的方法,包括json.Marshal、json.Unmarshal、json.NewDecoder的使用,以及如何自定义转换结果。强调了json.Tag的用法,如omitempty和-的选择性导出,并探讨了json.RawMessage的延时转换特性。

57f6d8d6398d3a8f119eb27cb4e7b8f8.png

「每一个程序员都无法逃脱 JSON 的命运魔爪」

JSON 简直就是一个神奇的玩意,只要是人类可以阅读的数据结构,基本都可以转成 JSON 的数据格式,其在各个平台、组件、模块中穿梭不止,使用上更是游刃有余。甚至在 HTTP 接口上,有取代 FormData 的趋势(上传文件还是得 Form),成为 POST 数据的新宠儿。在这里我们需要感谢 Javascript,感谢前端工程师。

数据类型JSONGolang
字串stringstring
整数numberint64
浮点数numberflaot64
数组arraryslice
对象objectstruct
布尔boolbool
空值nullnil

JSON 数据类型对照表

下面我将为大家呈现 JSON 在 Golang 中的应用。

一、 入门

Golang 中提供了 json 的标准库,可以完成有关 JSON 的数据转换功能。

json.Marshal

Marshal 大法好,可以把一切 Struct 抹成 JSON , 把每个 Struct 收拾的服服帖帖,毫无退路可言。

在 Struct 中可以通过 json Tag 来给 JSON 转换的结果指定键值:

code:type People struct {  Name string  `json:"nickname"`  Age  int64 `json:"age"`}func main(){  j := People{    Name : "Haha",    Age : 18,  }  jsonByte, _ := json.MarshalIndent(j, "" ,"  ")  fmt.Println(string(jsonByte))}output:{  "nickname": "Haha",  "age": 18}

你可能注意到,我并没有使用 json.Marshal 方法,而是使用了 json.MarshalIntent 方法。

json.MarshalIntent 可以称之为 json.Marshal 的漂亮版,简称 「马漂亮」,是用来打日志的一把好手。

json.Unmarshal

正如其名,此方法为将 JSON 的字节,转换成具体指定的数据结构。我们来试验一下。

接上 code:m := make(map[string]interface{})_ = json.Unmarshal(jsonByte, m)fmt.Println(m)output:map[]%

好像没有得到预期的答案?对了, json.Unmarshal 的第二个参数需要是指针类型才行,由于 Golang 中所有参数都是值传递,原始参数不能被函数内部所修改。

接上 code:m := make(map[string]interface{})_ = json.Unmarshal(jsonByte, &m)fmt.Println(m)output:map[age:18 nickname:Haha]

我们如期的得到了转化后的值,是一个 map 类型, key 为 Struct json tag 指定的 key , 值为具体的 JSON 对应数值。

但是这里有个陷阱, json.Unmarshal 默认会把所有的数字都转化为 float64 类型,而不是 int64 类型。不信的话可以测试一下:

fmt.Println(m["age"].(int64))output:panic: interface conversion: interface {} is float64, not int64

至此我们得到了一个痛的领悟。

所以如果我们想要得到一个 int64 类型的值,则需要强制类型转换一下:

fmt.Println(int64(m["age"].(float64)))output:18

不过除了强制类型转换,还有另外一个方式来解决此问题。

json.NewDecoder

json.NewDecoder 可以通过读取 io.Reader 类型的参数来直接使用 stream 数据,在获取 HTTP POST 数据的时候使用会特别的舒爽。但是如果已知 []byte 二进制数组的话,建议还是直接使用 json.Marshal 方法比较简单。

json.NewDecoder 返回了 Decoder 结构体实例,此结构体有一个叫 UseNumber 的方法,此方法可以定义在转换过程中,将 JSON 的数字转换为 json.Number 类型,而 json.Number 又有 Int64 / Float64 / String 三个方法,可以将值优雅地处理为你想要的结果。

改造上面的代码,变为:

code:j := People{  Name : "Haha",  Age : 18,}jsonByte, _ := json.MarshalIndent(j, "" ,"  ")jDecoder := json.NewDecoder(strings.NewReader( string(jsonByte)))m := make(map[string]interface{})jDecoder.UseNumber()_ = jDecoder.Decode(&m)fmt.Println(m["age"].(json.Number).Int64())output:18 <nil>

这样看,是不是更优雅了一些,更「Golang」了一些。

其实 json.Number 里的三个类型转换方法做的事情超级简单,就是调用了 系统提供的 strconv 中的一些方法来实现类型转换,部分代码如下:

pakcage json// A Number represents a JSON number literal.type Number string// String returns the literal text of the number.func (n Number) String() string { return string(n) }// Float64 returns the number as a float64.func (n Number) Float64() (float64, error) {  return strconv.ParseFloat(string(n), 64)}// Int64 returns the number as an int64.func (n Number) Int64() (int64, error) {  return strconv.ParseInt(string(n), 10, 64)}

二、升华

在项目中,难免会遇到坑,优秀的程序员会在坑里躺一会儿,就能想到填坑的办法。

- 选择性导出

对,没有看错,是「-」,在 json 的标签中添加 - ,而不是具体的键值,由此在 json.Marshal 后,将不再导出此键值。这样的话,可以处理在 Struct 中可以导出(不可导出的成员同时也不能被 json.Marshal 导出),但是 JSON  结果中不可导出的尴尬情景。

code:type People struct {  Name string  `json:"nickname"`  Age  int64 `json:"age"`  Gender  string `json:"-"`}output:{  "nickname": "Haha",  "age": 18}

omitempty 空值不导出

如果你有幸遇到十分苛刻有洁癖的前端工程师,他们不希望你返回「null」值,如果字段是 null,应该直接不返回该字段。除了自己写个递归把接口层返回值都筛选一遍外,还可以在 Struct json tag 里加入「omitempty」佐料。这样就标记了在 json.Marshal 的时候,如果此值为 null ,自动不导出此值,比如

code:type People struct {  Name string  `json:"nickname"`  Age  int64 `json:"age"`  Gender  string `json:"-"`  Body  interface{} `json:"body,omitempty"`}cond 1:j := People{  Name : "Haha",  Age : 18,  Body: "waaaaa",}output:{  "nickname": "Haha",  "age": 18,  "body": "waaaaa"}cond 2:j := People{  Name : "Haha",  Age : 18,}output:{  "nickname": "Haha",  "age": 18}而不是{  "nickname": "Haha",  "age": 18,  "body": null}

空值也导出

但是也有一些时候,你需要导出空值,但是是对应类型的空值,而不是 null。比如空数组是 [] , 空对象是 {}。那么在导出的时候,需要确认对应的值已经被正确的初始化并分配内存。比如空切片,

var address []int64

的 JSON 导出为 null,但是

address := []int64{}

的 JSON 导出为 []。这两个的区别就是第二个语句为变量分配了内存空间,不过仍然是一个空的切片。而第一个语句只是一个定义。

再比如 map

var body map[string]string

的 JSON 导出为 null, 但是

body := make(map[string]string)

的 JSON 导出为 {} 。

三、飞天

MarshalJSON 和 UnmarshalJSON 自定义转换结果

在更变态的需求中,可能需要 Struct 直接自定义一个 JSON 返回结构,而不是导出自己的成员变量。这时候就要祭出 json 大杀器,MarshalJSON 和 UnmarshalJSON。

这一对方法,可以直接返回 json 的转换结构,忽略结构体本来的面目。

code:type People struct {  Name string  `json:"nickname"`  Age  int64 `json:"age"`  Gender  string `json:"-"`  Body  interface{} `json:"body,omitempty"`  Address []string `json:"address"`}func (p People) MarshalJSON() ([]byte, error){  return []byte("{\"a\": 123} "), nil}func (p *People) UnmarshalJSON(data []byte) error{  *p = People{    Name:    "Forbidden",  }  return nil}j := People{  Name : "Haha",  Age : 18,}jsonByte, _ := json.MarshalIndent(j, "" ,"  ")fmt.Println(string(jsonByte))m := People{}_ = json.Unmarshal(jsonByte, &m)fmt.Println(m)output:{  "a": 123}{Forbidden 0  <nil> []}

通过 MarshalJSON 和 UnmarhsalJSON,就可以直接控制 JSON 转换的结果,如庖丁解牛,一丝不挂。

不过需要注意的是, MarshalJSON 方法是非指针的,而 UnmarshalJSON 方法是指针类型的。

json.RawMessage 延时转换

RawMessage 可以更细粒度的控制解码节奏,如果一个 Struct 成员被设置为 json.RawMessage 类型,那么它就需要被单独 Unmarshal 才行,否则它一直保持二进制的样子,比如:

type People struct {  Name string  `json:"nickname"`  Age  int64 `json:"age"`  Address json.RawMessage `json:"address"` }address, _ := json.Marshal("加利福尼亚")j := People{  Name : "Haha",  Age : 18,  Address: address,}jsonByte, _ := json.MarshalIndent(j, "" ,"  ")fmt.Println(string(jsonByte))m :=  People{}_ = json.Unmarshal(jsonByte, &m)fmt.Println(m)var any  interface{}_ = json.Unmarshal(m.Address, &any)fmt.Println(any)output:{  "nickname": "Haha",  "age": 18,  "address": "加利福尼亚"}{Haha 18   [34 229 138 160 229 136 169 231 166 143 229 176 188 228 186 154 34]}加利福尼亚

如果不单独解码,则 Address 一直是 []byte 的数据格式。除非单独对其进行 Unmashal 解码。

四、总结

优雅地处理 JSON,能让后端对你刮目相看,更能和前端和谐共处,让世界变得更美好。

185faef569515e24a3fe0388b59f5e46.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值