json获取属性的方法 java 获取json的key_JSON

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

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

数据类型

JSON

Golang

字串

string

string

整数

number

int64

浮点数

number

flaot64

数组

arrary

slice

对象

object

struct

布尔

bool

bool

空值

null

nil

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,能让后端对你刮目相看,更能和前端和谐共处,让世界变得更美好。