Go反射应用案例分析--Json数据解析

Golang encoding/json包基于Struct类型的Tag特性,提供了JSON解析方法。这里正是反射的常见用法之一。
包中最重要的两个函数如下:

  • Marshal(v interface{}) ([]byte, error) : 将Go数据类型软换为JSON格式数据
  • Unmarshal(data []byte, v interface{}):将JSON格式数据转换为Go数据类型。

JSON包中的Tag约定

Struct的Tag实际上仅代表一个字符串而矣,至于字符串格式则是各package的约定而矣,encoding/json包中把json作为key,标明该tag可以其处理,其他的key值不在其处理范围。

针对Tag的写法,encoding/json作了如下约定:

  • json:"Foo" : Marshal时将结构体该字段名以“Foo”名输出,Unmarshal时将JSON相应字段赋值给结构体字段
  • json:"-": 无论Marshal和Unmarshal都忽略该字段
  • json:",omitempty":仅用于Marshal,如果结构体该字段为空,则忽略该字段。注意前面的逗号要保留。
  • json:"Foo,omitempty":组合写法,含义同上

很简单的几个约定,不是吗。 来看一例子,使用一下上面的约定:

package main

import (
    "encoding/json"
    "fmt"
)

type Server struct {
    ServerName  string   `json:"ServerName,omitempty"`
    ServerIP    string   `json:"ServerAddr"`
    ServerOwner string   `json:"-"`
    other       string   `json:"other"`
}

func main() {
    str := `{"ServerName": "Shanghai_VPN", "ServerAddr": "127.0.0.1", "ServerOwner": "Rainbow", "other": "You can see me!"}`
    var s Server

    json.Unmarshal([]byte(str), &s)

    fmt.Printf("s.ServerName: %s\n", s.ServerName)
    fmt.Printf("s.ServerIP: %s\n", s.ServerIP)
    fmt.Printf("s.ServerOwner: %s\n", s.ServerOwner)
    fmt.Printf("s.other: %s\n", s.other)

    s.ServerName = ""
    jsonStr,_ := json.Marshal(s)
    fmt.Printf("Marshal: %s", jsonStr)
}

上述代码声明一个Server结构体,并场景了Tag. 定义一个变量s,Unmarshal将某JSON字符串中的数据填入s中,Marshal将s中的内容,输出为一段JSON数据。
输出如下:

s.ServerName: Shanghai_VPN //源自JSON数据中的"ServerName"
s.ServerIP: 127.0.0.1      //源自JSON数据中的"ServerAddr"
s.ServerOwner:             //跟据Tag指示,该字段会被忽略
s.other:                   //json包不处理未被导出的成员
Marshal: {"ServerAddr":"127.0.0.1"} //跟据Tag指示,如果ServerName为空,则不输出

JSON转换规则

以Unmarshal为例说明规则,假定JSON数据中有个key名为"Foo",那么确定结构体的字段名字遵循如下规则:

  1. 可导出成员中, 是否存在Tag名为"Foo"的字段
  2. 可导出成员中,是否存在成员名为"Foo"的字段
  3. 可导出成员中,是否存在成员名为"FOo"或"FoO"(首字母大小,其余字段忽略大小写)的字段

所以说,如果JSON数据与结构体成员名一一对应,没有Tag也可以实现解析。换句话说,Tag可以方便的处理结构体成员名与JSON数据不一致的情况。

JSON数据类型与Go数据类型对照

Go数据类型JSON数据类型
boolJSON booleans
float64JSON numbers
stringJSON strings
nilJSON null

上例中结构体成员都是string类型,其实也可以是其他常见类型,对此,不再细述,参照上表处理即可。

未知JSON数据解析

注意,Unmarshal方法中将JSON数据填入一个interface{}变量,所以不管任何JSON数据都可以解析。无非就是再从空interface中提取数据罢了。一般情况下,json包会将JSON解析为一个map[string]interface{}

踩过的坑

关于omitempty,要注意它会屏蔽一切的初始值和空值。比如,整型,即便赋值为0,其也不会被转换为JSON字符串。 有时,你希望结构体存储的是某个状态,而状态中正好有个值为“零”,这时就要小心了。
官方对omitempty的解释很清楚
The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.

再不清楚,看下面代码:

package main

import (
    "fmt"
    "encoding/json"
    "github.com/bitly/go-simplejson"
)

type device struct {
    DeviceID        string                      `json:"DeviceID"`
    ServerState     int                         `json:"ServerState,omitempty"`
}



func main() {

    var d device
    d.DeviceID = "123"
    d.ServerState = 0

    data, _  := json.Marshal(d)

    json, _ := simplejson.NewJson(data)

    fmt.Printf("%v", json)
}

上面代码中ServerState即便赋值了,仍然不会输出。

程序输出如下:

&{map[DeviceID:123]}

转载于:https://my.oschina.net/renhc/blog/2001946

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值