您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息
免费发信息

Go中的JSON

2024/4/18 17:53:39发布7次查看
用过php的人都知道,php处理json数据那是相当方便,json_encode和json_decode两个函数搞定一切。那么在go中该怎么处理json呢?
一、encoding/json标准库学习 json 库应该先了解 go 中的 struct tag、reflect等知识。
1、概述json包实现了json对象的编解码,参见rfc 4627。json对象和go类型的映射关系请参见marshal和unmarshal函数的文档。
参见”json and go”获取本包的一个介绍
2、核心函数和类型对于函数和类型,我们关注经常使用的。
1)marshal 和 unmarshal这两个是最常使用的函数,也就是 json 对象的编解码。这两个函数的文档很长,详细解释了 go 类型和 json 对象的映射关系等。映射关系整理如下:
bool, for json booleans
float64, for json numbers
string, for json strings
[]interface{}, for json arrays
map[string]interface{}, for json objects
nil for json null
详细的编码解码规则,文档上解释的很详细,这里说几个关键点:
①默认情况下,按照上面提到的映射进行解析;
②如果对象实现了 json.marshaler/unmarshaler 接口且不是 nil 指针,则调用对应的方法进行编解码;如果没有实现该接口,但实现了 encoding.textmarshaler/textunmarshaler 接口,则调用该接口的相应方法进行编解码;
③struct 中通过 “json” tag 来控制相关编解码,后面通过示例说明;
④struct 的匿名字段,默认展开;可以通过指定 tag 来使其不展开;
⑤如果存在匿名字段,如果同级别有相同字段名,不会冲突,具体处理规则文档有说明;
⑥在解码时到struct时,会忽略多余或不存在的字段(包括不导出的),而不会报错;
另外注意,传递给 unmarshal 的第二个参数必须是指针。
使用示例:
package mainimport ( "encoding/json" "fmt")func main() { type book struct { name string price float64 // `json:"price,string"` } var person = struct { name string age int book }{ name: "polaris", age: 30, book: book{ price: 3.4, name: "go语言", }, } buf, _ := json.marshal(person) fmt.println(string(buf)) // output:{"name":"polaris","age":30,"price":3.4} // book 中的 name 被忽略了}
如果不希望内嵌类型展开,只需加上 tag:
var person = struct { name string age int book `json:"book"`}
有时候,比如之前是 php(弱类型语言) 写的,age 的值很可能是 “age”:”30” 这种形式,现在改为用 go 实现,为了兼容;或者返回给客户端 price 这样的浮点值,可能会涉及精度问题,客户端只是单纯的展示,返回浮点值的字符串即可。针对这样的情况,只需要加上这样的 tag:`json:”,string” 即可。这里逗号后面的“string”是 tag option。
如果想忽略某个字段,加上`json:”-”`;如果在值为空时忽略,加上omitempty option,如:`json:”,omitempty”`
在解码时,优先匹配 struct 导出字段的 tag,之后是 field,最后是 field 的各种大小写不明感的形式,如 name,能匹配 name/name等等。
2)marshalindent 函数该函数的功能和 marshal一致,只是格式化 json,方便人工阅读。如上面例子使用该函数,marshalindent(person, “”, “\t”) 输出如下:
{ "name": "polaris", "age": 30, "price": 3.4}
3)encoder 和 decoder
有时候,我们可能从 request 之类的输入流中直接读取 json 进行解析或将编码的 json 直接输出,为了方便,标准库为我们提供了 decoder 和 encoder 类型。它们分别通过一个 io.reader 和 io.writer 实例化,并从中读取数据或写数据。
通过阅读源码可以发现,encoder.encode/decoder.decode 和 marshal/unmarshal 实现大体是一样;有一些不同点:decoder 有一个方法 usenumber,它的作用:默认情况下,json 的 number 会映射为 go 中的 float64,有时候,这会有些问题,比如:
b := []byte(`{"name":"polaris","age":30,"money":20.3}`)var person = make(map[string]interface{})err := json.unmarshal(b, &person)if err != nil { log.fatalln("json unmarshal error:", err)}age := person["age"]log.println(age.(int))
我们希望 age 是 int,结果 panic 了:interface conversion: interface is float64, not int.
我们改为 decoder.decode(用上 usenumber) 试试:
b := []byte(`{"name":"polaris","age":30,"money":20.3}`)var person = make(map[string]interface{})decoder := json.newdecoder(bytes.newreader(b))decoder.usenumber()err := decoder.decode(&person)if err != nil { log.fatalln("json unmarshal error:", err)}age := person["age"]log.println(age.(json.number).int64())
我们使用了 json.number 类型。
4)rawmessage 类型该类型的定义是 type rawmessage []byte,可见保存的是原始的 json 对象,它实现了 marshaler 和 unmarshaler 接口,能够延迟对 json 进行解码。使用示例可以参考 http://docs.studygolang.com/pkg/encoding/json/#rawmessage上的例子。
5)其他函数或类型不常用(如错误类型等),在此不赘述,可以直接查阅官方文档。二、实际应用中的问题
当客户端和服务器通讯使用 json 这种数据格式时,我们一方面会解码客户端的 json 数据,另一方面,需要对数据进行 json 编码,发送给客户端。
一般的,服务器发送给客户端的 json 数据,是通过 struct、[]struct 或 map[string]interface{} 等编码得到。这里除了上文说到的,可能需要对数值类型使用 string tag options 之外,对于 time.time 类型(实现了marshaler 接口),默认编码得到的时间格式是:rfc3339即2006-01-02t15:04:05z07:00,很多时候客户端可能不希望得到这样的时间,他们更多时候只是需要一个可读的时间字符串,如 2006-01-02 15:04:05。对此,我们可以定义自己的类型 type oftentime time:
func (self oftentime) marshaljson() ([]byte, error) { t := time.time(self) if y := t.year(); y < 0 || y >= 10000 { return nil, errors.new("time.marshaljson: year outside of range [0,9999]") } return []byte(t.format(`"2006-01-02 15:04:05"`)), nil}func (this *oftentime) unmarshaljson(data []byte) (err error) { t := time.time(*this) return t.unmarshaljson(data)}
另外,有一个坑,json 对象的 key 必须是字符串,所以 map[int]interface{} 在编码时会报错,错误是 json.unsupportedtypeerror.
对于接收客户端数据,进行 json 解码,遇到的问题可能比较多,特别是同时接收多种语言的数据,比如 php、java 等。比如 b := []byte(`{“name”:”polaris”,”age”:30,”money”:20.3}`),php 传递过来的可能是:b := []byte(`{“name”:”polaris”,”age”:”30″,”money”:”20.3″}`),在使用 struct 接收数据时,对于 age,如果是 int,我们可以直接定义为 int 类型,但如果是string,可以通过 string tag options 接收;但如果age有时是 int, 有时是 string,就会出问题。最理想的情况,当然是不希望出现这种情况,但有一点,程序要保证出现这种情况时,不能 panic。
在实际应用中,我就遇到了上面的问题,于是,自己写了一个 json 解析,能支持自动类型转换。代码开源在 github: https://github.com/polaris1119/jsonutils
三、性能问题很明显,json 的编解码,使用了 go 的反射功能,所以,性能自然不是太好,正因为如此,有了 ffjson、easyjson 之类的开源库(在 github 上),它们的原理是通过 go generate 根据 struct 生成相应的代码,避免反射。如果你对性能要求比较高,但又不想使用msgpack/pb/thrift 之类的,那么可以考虑使用 ffjson/easyjson 来优化性能。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录