JSON

Harries Blog

字号+ 作者:H5之家 来源:H5之家 2016-05-21 10:00 我要评论( )

用过PHP的人都知道,PHP处理JSON数据那是相当方便,json_encode和json_decode两个函数搞定一切。那么在Go中该怎么处理JSON呢?

用过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”获取本包的一个介绍: http://golang.org/doc/articles/json_and_go.html 这是官方的一篇博文。

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 main import ( "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 进行解码。使用示例可以参考 #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.

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • 创建 REST API 的最佳入门教程

    创建 REST API 的最佳入门教程

    2015-11-23 17:25

  • 【Git 项目推荐】目前为止性能最强的 JSON 框架

    【Git 项目推荐】目前为止性能最强的 JSON 框架

    2015-11-21 13:18

  • 请求路由到业务方法设计(2)

    请求路由到业务方法设计(2)

    2015-11-10 14:41

  • android软件开发之webView.addJavascriptInterface循环渐进【一

    android软件开发之webView.addJavascriptInterface循环渐进【一

    2015-10-27 10:34

网友点评