json作为一种常用的结构化数据格式,在处理配置数据或业务数据时使用的非常频繁,在golang的标准库中自带了json的编解码模块 encoding/json 本文将介绍如何利用标准库来读写json文件。

简单示例

json 有4种基本的数据类型:string, number, boolean, null, 一个简单的包含这4个示例的 json 文件如下:

  {
    "name":"root",
    "age": 18,
    "isMale":true,
    "city":null
  }

从外观上来看,json文件的结构很像golang的结构体:

  type user struct {
          name string
          age uint8
          isMale bool
          city string
  }

实际上golang在解析json文件的时候也确实是把json对象的字段一一映射到golang的结构提中的。这个用来执行映射过程的函数就是 json.Unmarshal(json, &struct) ,完整的示例如下:

  package main

  import (
          "encoding/json"
          "fmt"
  )

  type user struct {
          name string
          age uint8
          isMale bool
          city string
  }

  func main() {
          jsonText := `
    {
      "name":"root",
      "age": 18,
      "isMale":true,
      "city":null
    }
  `
          var u user
          json.Unmarshal([]byte(jsonText), &u)
          fmt.Println(u)
  }

运行上面的代码后却发现输出的内容是 { 0 false } ,说好的一一对应呢?为什么没有解析出对应的值?

首先golang的struct有一个基本的原则:小写字母开头的成员只能包内使用,大写字母开头的成员才能被别的包访问。上面的示例中, json 包通过Unmarshal函数对 main 包的 user 结构成员赋值,而 main 包中的user成员名都是小写字母开头,所以 json 包无法访问。所以,接下来需要把所有user的成员名的首字母都改成大写:

  type user struct {
          Name string
          Age uint8
          IsMale bool
          City string
  }

改完以后再次运行,却发现输出的内容依然是 { 0 false } , 这又是什么原因呢?

其实这个问题的原因就在于,json文本中的key名都是小写的,所以接下来在进行一次修改,把json的key名的首字母也改成大写的:

  package main

  import (
      "encoding/json"
      "fmt"
  )

  type user struct {
      Name   string
      Age    uint8
      IsMale bool
      City   string
  }

  func main() {
      jsonText := `
      {
        "Name":"root",
        "Age": 18,
        "IsMale":true,
        "City":null
      }
    `
      var u user
      json.Unmarshal([]byte(jsonText), &u)
      fmt.Println(u)
  }

这个时候在运行上面的代码,就会输出 {root 18 true } ,这个结果就是正确的了。同时我们也可以确定,json的null值映射到字符传字段时是一个空串。也可认为值为 null 的json对象是不存在的,Unmarshal在映射的时候会忽略这一项,被映射的结构成员就是其初始值,对于 bool 而言,初始值是 false ,对于 uint8 而言,初始值是 0 ,对于 string 而言,初始值就是空串 ""

结构标签控制

在上面的示例中,为了能让结构体成员被 json 包访问,我们把成员名的首字母改成了大写,同时又为了 json 对象和结构成员能一一对应,我们又把 json 的key名改成了大写字母开头。然而在实际的使用过程中,大部分的 json 都是小写字母开头的key名。退一步来说,如果 json 文件由我们自己提供,那么我们可以统一约定key名全部用大写字母开头,但是如果对接第三方产生的 json 文件又怎么处理呢?

同样还有一些其它的问题:

  1. 结构的成员以大写字母开头是为了给其它包使用,但是不想该成员参与json的解析。

  2. 如果结构成员的值为空,那么在解析的时候就忽略该值。

  3. 结构成员的类型是 int64 类型,输出的json的时候,为了兼容其它没有 int64 类型的模块,需要将其转为 string 类型输出。

为了实现这些控制,golang的 json 包通过 tag 来对结构成员进行修饰。如需要结构成员和小写的json的key一一对应,只需在每个成员后加上 `json:"<key-nam>"` 这样格式的标签即可。

  type user struct {
          Name string `json:"name"`
          Age uint8 `json:"age"`
          IsMale bool `json:"isMale"`
          City string `json:"city"`
  }

更多tag的用法详见:https://golang.org/pkg/encoding/json/

  // 结构成员 Field 在json文件中的名字是 myName
  Field int `json:"myName"`

  // 结构成员 Field 在json文件中的名字是 myName
  // 如果 Field 的值为空,那么该成员就不会出现在json对象中
  Field int `json:"myName,omitempty"`

  // 结构成员 Field 在json文件中的名字是 Field (默认,不改变json中的名字)
  // 如果 Field 的值为空,那么该成员就不会出现在json对象中
  // 注意这个(omitempty)前导逗号“,”
  Field int `json:",omitempty"`

  // json包忽略 Filed 这个字段
  Field int `json:"-"`


  Field int `json:"-,"`

  // 结构成员 Int64String 在json文件中的名字是 Int64String
  // 但其输出的json内容不是number类型,而是string类型,如 "Int64String" : "1234567890"
  Int64String int64 `json:",string"`

复合类型JSON

数组

同类型成员

  {
    "name":"John",
    "scores":[80,75,90,95]
  }

不通类型成员

  {
    "id":1000,
    "info":["Mark", 18, true]
  }

对象

固定对象

  {
    "base-info":{
      "name":"Bob",
      "age":25,
      "gender":"male"
    }
  }

不定对象

  {
    "extra-info": {
      "key1":"value1",
      "key2":"value2",
      "key3":"value3",
      "...":"value..."
      }
    }