Go 语言 JSON 简介

yuhai4v5z 5年前
   <h2>简介</h2>    <p>JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式,因为易读性、机器容易处理而变得流行。</p>    <p>JSON 语言定义的内容非常简洁,主要分为三种类型:对象(object)、数组(array)和基本类型(value)。基本类型(value)包括:</p>    <ul>     <li>string 字符串,双引号括起来的 unciode 字符序列</li>     <li>number 数字,可以是整数,也可以是浮点数,但是不支持八进制和十六进制表示的数字</li>     <li>true,false 真值和假值,一般对应语言中的 bool 类型</li>     <li>null 空值,对应于语言中的空指针等</li>    </ul>    <p>数组(array)就是方括号括 [] 起来的任意值的序列,中间以逗号 , 隔开。对象(object)是一系列无序的键值组合, <strong>键必须是字符串</strong> ,键值对之间以逗号 , 隔开,键和值以冒号 : 隔开。数组和对象中的值都可以是嵌套的。</p>    <p>JSON 官网 有非常易懂的图示,进一步了解可以移步。</p>    <p>JSON 不依赖于任何具体的语言,但是和大多数 C 家族的编程语言数据结构特别相似,所以 JSON 成了多语言之间数据交换的流行格式。Go 语言也不例外,标准库 encoding/json 就是专门处理 JSON 转换的。</p>    <p>这篇文章就专门介绍 Go 语言中怎么和 JSON 打交道,常用的模式以及需要注意的事项。</p>    <h2>使用</h2>    <p>Golang 的 encoding/json 库已经提供了很好的封装,可以让我们很方便地进行 JSON 数据的转换。</p>    <p>Go 语言中数据结构和 JSON 类型的对应关系如下表:</p>    <table>     <thead>      <tr>       <th>golang 类型</th>       <th>JSON 类型</th>       <th>注意事项</th>      </tr>     </thead>     <tbody>      <tr>       <td>bool</td>       <td>JSON booleans</td>       <td> </td>      </tr>      <tr>       <td>浮点数、整数</td>       <td>JSON numbers</td>       <td> </td>      </tr>      <tr>       <td>字符串</td>       <td>JSON strings</td>       <td>字符串会转换成 UTF-8 进行输出,无法转换的会打印对应的 unicode 值。而且为了防止浏览器把 json 输出当做 html, “<”、”>” 以及 “&” 会被转义为 “\u003c”、”\u003e” 和 “\u0026”。</td>      </tr>      <tr>       <td>array,slice</td>       <td>JSON arrays</td>       <td>[]byte 会被转换为 base64 字符串,nil slice 会被转换为 JSON null</td>      </tr>      <tr>       <td>struct</td>       <td>JSON objects</td>       <td>只有导出的字段(以大写字母开头)才会在输出中</td>      </tr>     </tbody>    </table>    <p>NOTE:Go 语言中一些特殊的类型,比如 Channel、complex、function 是不能被解析成 JSON 的。</p>    <h3>Encode 和 Decode</h3>    <p>要把 golang 的数据结构转换成 JSON 字符串(encode),可以使用 Marshal 函数:</p>    <pre>  <code class="language-go">func Marshal(v interface{}) ([]byte, error)</code></pre>    <p>比如我们有结构体 User</p>    <pre>  <code class="language-go">type User struct {      Name string      IsAdmin bool      Followers uint  }</code></pre>    <p>以及一个实例:</p>    <pre>  <code class="language-go">user := User{    Name:      "cizixs",    IsAdmin:   true,    Followers: 36,   }  data, err := json.Marshal(user)</code></pre>    <p>那么 data 就是 []byte 类型的数组,里面包含了解析为 JSON 之后的数据:</p>    <pre>  <code class="language-go">data == []byte(`{"Name":"cizixs","IsAdmin":true,"Followers":36}`)</code></pre>    <p>相对应的,要把 JSON 数据转换成 Go 类型的值(Decode), 可以使用 json.Unmarshal 。它的定义是这样的:</p>    <pre>  <code class="language-go">func Unmarshal(data []byte, v interface{}) error</code></pre>    <p>data 中存放的是 JSON 值,v 会存放解析后的数据,所以必须是指针,可以保证函数中做的修改能保存下来。</p>    <p>下面看个例子:</p>    <pre>  <code class="language-go">data = []byte(`{"Name":"gopher","IsAdmin":false,"Followers":8900}`)  var newUser = new(User)  err = json.Unmarshal(data, &newUser)  if err != nil {   fmt.Errorf("Can not decode data: %v\n", err)  }  fmt.Printf("%v\n", newUser)</code></pre>    <p>那么 Unmarshal 是怎么找到结构体中对应的值呢?比如给定一个 JSON key Filed ,它是这样查找的:</p>    <ul>     <li>首先查找 tag 名字(关于 JSON tag 的解释参看下一节)为 Field 的字段</li>     <li>然后查找名字为 Field 的字段</li>     <li>最后再找名字为 FiElD 等大小写不敏感的匹配字段。</li>     <li>如果都没有找到,就直接忽略这个 key,也不会报错。这对于要从众多数据中只选择部分来使用非常方便。</li>    </ul>    <h3>更多控制:Tag</h3>    <p>在定义 struct 字段的时候,可以在字段后面添加 tag,来控制 encode/decode 的过程:是否要 decode/encode 某个字段,JSON 中的字段名称是什么。</p>    <p>可以选择的控制字段有三种:</p>    <ul>     <li>- :不要解析这个字段</li>     <li>omitempty :当字段为空(默认值)时,不要解析这个字段。比如 false、0、nil、长度为 0 的 array,map,slice,string</li>     <li>FieldName :当解析 json 的时候,使用这个名字</li>    </ul>    <p>举例来说吧:</p>    <pre>  <code class="language-go">// 解析的时候忽略该字段。默认情况下会解析这个字段,因为它是大写字母开头的  Field int   `json:"-"`    // 解析(encode/decode) 的时候,使用 `other_name`,而不是 `Field`  Field int   `json:"other_name"`    // 解析的时候使用 `other_name`,如果struct 中这个值为空,就忽略它  Field int   `json:"other_name,omitempty"`</code></pre>    <h3>解析动态内容: interface{}</h3>    <p>上面的解析过程有一个假设——你要事先知道要解析的 JSON 内容格式,然后定义好对应的数据结构。如果你不知道要解析的内容呢? Go 提供了 interface{} 的格式,这个接口没有限定任何的方法,因此所有的类型都是满足这个接口的。在解析 JSON 的时候,任意动态的内容都可以解析成 interface{} 。</p>    <p>比如还是上面的数据,我们可以这样做:</p>    <pre>  <code class="language-go">data := []byte(`{"Name":"cizixs","IsAdmin":true,"Followers":36}`)    var f interface{}  json.Unmarshal(data, &f)</code></pre>    <p>但是要使用 f ,还是很麻烦的,我们要使用 type assertion :</p>    <pre>  <code class="language-go">name := f.(map[string]interface{})["Name"].(string)</code></pre>    <p>对于比较复杂的结构,这样的访问很麻烦,也很容易出错。</p>    <p>如果已经知道 JSON 数据是对象,而不是基本类型(bool,number,string,array)等,因为 JSON 对象键都是字符串,所以可以把上面的例子修改为:</p>    <pre>  <code class="language-go">var f map[string]interface{}    // 省去了上面 f 的 type assertion 步骤  name := f["Name"].(string)</code></pre>    <p>需要注意的是,尽管 Followers 字段没有小数点,我们希望它是整数值,解析的时候它还是会被解析成 float64 ,如果直接把它当做 int 访问,会出现错误:</p>    <pre>  <code class="language-go">followers := f["Followers"].(int)    // panic: interface conversion: interface is float64, not int</code></pre>    <p>而必须自己做类型转换:</p>    <pre>  <code class="language-go">followers := int(f["Followers"].(float64))</code></pre>    <h3>延迟解析:json.RawMessage</h3>    <p>在解析的时候,还可以把某部分先保留为 JSON 数据不要解析,等到后面得到更多信息的时候再去解析。继续拿 User 举例,比如我们要添加认证的信息,认证可以是用户名和密码,也可以是 token 认证。</p>    <pre>  <code class="language-go">type BasicAuth struct {      Email string      Password string  }    type TokenAuth struct {      Token string  }    type User struct {      Name string      IsAdmin bool      Followers uint      Auth json.RawMessage  }</code></pre>    <p>我们在定义 User 结构体的时候,把认证字段的类型定义为 json.RawMessage ,这样解析 JSON 数据的时候,对应的字段会先不急着转换成 Go 数据结构。然后我们可以自己去再次调用 Unmarshal 去读取里面的值:</p>    <pre>  <code class="language-go">err := json.Unmarshal(data, &basicAuth)  if basicAuth.Email != "" {      // 这是用户名/密码认证方式,在这里继续做一些处理  } else {      json.Unmarshal(data, &tokenAuth)      if tokenAuth.Token != "" {          // 这是 token 认证方法      }  }</code></pre>    <h3>自定义解析方法</h3>    <p>如果希望自己控制怎么解析成 JSON,或者把 JSON 解析成自定义的类型,只需要实现对应的接口(interface)。 encoding/json 提供了两个接口: Marshaler 和 Unmarshaler :</p>    <pre>  <code class="language-go">// Marshaler 接口定义了怎么把某个类型 encode 成 JSON 数据  type Marshaler interface {          MarshalJSON() ([]byte, error)  }    // Unmarshaler 接口定义了怎么把 JSON 数据 decode 成特定的类型数据。如果后续还要使用 JSON 数据,必须把数据拷贝一份  type Unmarshaler interface {          UnmarshalJSON([]byte) error  }</code></pre>    <p>标准库 time.Time 就实现了这两个接口。另外一个简单的例子(这个例子来自于参考资料中 Go and JSON 文章):</p>    <pre>  <code class="language-go">type Month struct {      MonthNumber int      YearNumber int  }    func (m Month) MarshalJSON() ([]byte, error){      return []byte(fmt.Sprintf("%d/%d", m.MonthNumber, m.YearNumber)), nil  }    func (m *Month) UnmarshalJSON(value []byte) error {      parts := strings.Split(string(value), "/")      m.MonthNumber = strconv.ParseInt(parts[0], 10, 32)      m.YearNumber = strconv.ParseInt(parts[1], 10, 32)        return nil  }</code></pre>    <h3>和 stream 中 JSON 打交道</h3>    <p>上面所有的 JSON 数据来源都是预先定义的 []byte 缓存,在很多时候,如果能读取/写入其他地方的数据就好了。 encoding/json 库中有两个专门处理这个事情的结构: Decoder 和 Encoder :</p>    <pre>  <code class="language-go">// Decoder 从 r io.Reader 中读取数据,`Decode(v interface{})` 方法把数据转换成对应的数据结构  func NewDecoder(r io.Reader) *Decoder    // Encoder 的 `Encode(v interface{})` 把数据结构转换成对应的 JSON 数据,然后写入到 w io.Writer 中  func NewEncoder(w io.Writer) *Encoder</code></pre>    <p>下面的例子就是从标准输入流中读取数据,解析成数据结构,删除所有键不是 Name 的字段,然后再 encode 成 JSON 数据,打印到标准输出。</p>    <pre>  <code class="language-go">package main    import (      "encoding/json"      "log"      "os"  )    func main() {      dec := json.NewDecoder(os.Stdin)      enc := json.NewEncoder(os.Stdout)      for {          var v map[string]interface{}          if err := dec.Decode(&v); err != nil {              log.Println(err)              return          }          for k := range v {              if k != "Name" {                  delete(v, k)              }          }          if err := enc.Encode(&v); err != nil {              log.Println(err)          }      }  }</code></pre>    <h2>参考资料</h2>    <ul>     <li><a href="/misc/goto?guid=4959730437057275697" rel="nofollow,noindex">Go Blog: JSON and Go</a></li>     <li><a href="/misc/goto?guid=4959730437146774849" rel="nofollow,noindex">Dynamic JSON in Go</a></li>     <li><a href="/misc/goto?guid=4959730437239539768" rel="nofollow,noindex">Go and JSON</a></li>    </ul>    <p> </p>    <p>来自:http://cizixs.com/2016/12/19/golang-json-guide</p>    <p> </p>