Go json

  • JSON(JavaScript Object Notation,JS对象标记)是一种比XML更为轻量的【数据交换格式】
  • JSON易于人阅读与编写,也易于程序生成和解析。

JSON虽是JavaScript的一个子集,但采用完全独立于编程语言的文本格式,表现为键值对集合(字典结构)的文本描述形式,使其成为较为理想、跨平台、跨语言的数据交换语言。

JSON格式可有效地提升网络传输效率,网络传输时会先将数据序列化为JSON字符串,接收方再反序列化为对应的数据类型。

encoding/json

  • Golang内置encoding/json标准库对JSON格式支持,实现生成和解析JSON格式的数据。
  • Golang实现JSON编解码时遵循RFC4627标准协议
  • Golang对JSON编解码时中间数据状态为字节数组[]byte
import "encoding/json"

JSON格式隶属于序列化格式,Go语言的强类型对格式要求严格,JSON格式虽然有类型但并不稳定。

encoding/json

流式读写Stream

  • encoding/json库提供了EncoderDecoder两种类型用于支持JSON数据的流式
  • encoding/json库提供了NewEncoder()NewDecoder()函数具体实现
  • 使用EncoderDecoder可以对数据流进行处理,比如读写HTTP连接、WebSocket、文件...
  • Golang标准库net/rpc/jsonrpc应用了EncoderDecoder

json.NewEncoder

func NewEncoder(w io.Writer) *Encoder {
    return &Encoder{w: w, escapeHTML: true}
}

json.NewDecoder

func NewDecoder(r io.Reader) *Decoder {
    return &Decoder{r: r}
}

strings.NewReader

  • 对于字符串可使用strings.NewReader()让字符串转换为一个Stream流对象
func strings.NewReader(s string) *strings.Reader { 
  return &Reader{s, 0, -1} 
}

strings.NewReader()会创建一个从字符串中读取数据的读取器strings.Reader,与strings.Builder不同的是strings.Reader类型是为了高效读取字符串而存在的。

json.Decoder

  • 调用json.NewDecoder()构造json.Decoder解码器对象,使用其Decode()解码方法为给定的结构体赋值。

例如:

type User struct{
    Id int `json:"id"`
    Name string `json:"name"`
}
jsonstr := `{"id":1, "name":"root"}`
reader := strings.NewReader(jsonstr)
decoder := json.NewDecoder(reader)

var user User
err := decoder.Decode(&user)
if err!=nil {
    panic(err)
}

fmt.Printf("%#v\n", user)
main.User{Id:1, Name:"root"}

json.Encoder

user := User{Id:1, Name:"root"}

buf := new(bytes.Buffer)
err := json.NewEncoder(buf).Encode(user)
if err!=nil {
    panic(err)
}

fmt.Printf("%s\n", buf)

json.Marshal

  • encoding/json库提供的json.Marshal()函数可对一组数据进行JSON格式的编码。
func json.Marshal(v interface{}) ([]byte, error)

数据类型编码注意事项

  • string会以UTF-8编码转化输出为Unicode字符集的字符串,特殊字符比如<会被转义为\u003c
  • []byte字节数组会被转换为Base64编码后的字符串
  • slice切片的零值会被转换为null
  • map转换时其数据类型必须为map[string]T
  • struct结构体会被转化为JSON对象,注意只有结构体中以大写字母开头的可被导出的字段才能被转化输出,这些可导出的字段会作为JSON对象的字符串索引。
Go JSON
struct/*struct Object
array/slice Array
map Object

Golang多数数据类型都可以转换为有效的JSON文本,但channel通道、complex、函数等类型除外。若转换前数据类型中出现指针则会转换指针所指向的指,若指针指向的是零值,则会将nil转换为结果。

type User struct{
    Id int `json:"id"`
    Name string `json:"name"`
    Colors []string `json:"colors"`
}

user := User{Id:1, Name:"root", Colors:[]string{"Red", "Green", "Blue", "Ruby"}}

buf := new(bytes.Buffer)
err := json.NewEncoder(buf).Encode(user)
if err!=nil {
    panic(err)
}

fmt.Printf("%s\n", buf)
{"id":1,"name":"root","colors":["Red","Green","Blue","Ruby"]}

通过对数据进行编码或序列化生成JSON格式

  • 对结构体struct编码生成JSON
  • 对映射map编码生成JSON

结构体编码生成JSON

使用json.Marshal()函数将结构实例生成JSON格式的文本

package main
import (
    "fmt"
    "encoding/json"
)

type User struct{
    Id int
    UserName string
    Active bool
    Balance float32
}

func main () {
    user := User{1, "admin", true, 1000.01}
    data, err := json.Marshal(user)
    if err!= nil{
        fmt.Printf("Error: %v\n", err)
    }
    
    fmt.Printf("%T %v\n", data, data)

    jsonstr := string(data)
    fmt.Printf("%T %v\n", jsonstr, jsonstr)
}
[]uint8 [123 34 73 100 34 58 49 44 34 85 115 101 114 78 97 109 101 34 58 34 97 100 109 105 110 34 44 34 65 99 116 105 118 101 34 58 116 114 117 101 44 34 66 97 108 97 110 99 101 34 58 49 48 48 48 46 48 49 125]
string {"Id":1,"UserName":"admin","Active":true,"Balance":1000.01}

json.Marshal()编码成功返回err将会被赋予零值nildata则是一个进行JSON格式化后的[]byte字节切片类型的变量。

调用json.Marshal()时会递归实例,若实例实现了json.Marshaler接口且包含有效值,Marshal()函数就会调用其MarshalJSON()方法将该数据结构转换为JSON格式的文本。

Go语言中大多数据类型都可以转化为有效地JSON文本,但channel通道、complex复数、function函数这几种类型除外。

字典映射生成JSON

package main
import (
    "fmt"
    "encoding/json"
)

func main () {

    dict := make(map[string]interface{})
    dict["id"] = 1
    dict["username"] = "admin"
    dict["active"] = true
    dict["balance"] = 1000.99
    dict["operator"] = []string{"root", "tester"}

    data, _ := json.MarshalIndent(dict, "", "    ")
    jsonstr := string(data)

    fmt.Printf("%T %v\n", jsonstr, jsonstr)
}
string {
    "active": true,
    "balance": 1000.99,
    "id": 1,
    "operator": [
        "root",
        "tester"
    ],
    "username": "admin"
}

转换前后数据类型

GO语言中JSON转换前后数据类型映射关系

转换前 转换后 描述
布尔值 布尔类型 -
浮点数/整数 常规数字 -
字符串 字符串 UTF-8编码字符串会被转换为Unicode字符集的字符串,特殊字符会转码,比如<会被转换为\u003c
数组 JSON数组 []byte类型的值会被转换为Base64编码后的字符串。
切片 JSON数组 切片类型的零值会被转换为null
结构体 JSON对象 只有结构体中以大写字母开头且可以被导出的字段才能被转换输出。
映射 JSON格式 只有数据类型为map[string]TTencoding/json库支持的任意数据类型才能转换。
package main
import (
    "fmt"
    "encoding/json"
)

func main () {
    user := struct{
        Id int32
        UserName string
        Active bool
        Scores map[string]int32
    }{
        1,
        "admin",
        false,
        map[string]int32{"Music":90, "English":100},
    }
    
    data, _ := json.Marshal(user)
    jsonstr := string(data)

    fmt.Printf("%T %v\n", jsonstr, jsonstr)
}
string {"Id":1,"UserName":"admin","Active":false,"Scores":{"English":100,"Music":90}}

结构体字段中若首字母为小写则JSON无权访问,输出的字段首字母全部为大写。若需要输出自定义的阶段名,比如小写,就需要使用结构体Tag标签。

package main
import (
    "fmt"
    "encoding/json"
)

func main () {
    user := struct{
        Id int32 `json:"id"`
        UserName string `json:"username"`
        Active bool `json:"active"`
        Scores map[string]int32 `json:"score_set"`
        Operator []string `json:"operator"`
    }{
        1,
        "admin",
        false,
        map[string]int32{"Music":90, "English":100},
        []string{"root", "tester"},
    }

    data, _ := json.Marshal(user)
    jsonstr := string(data)

    fmt.Printf("%T %v\n", jsonstr, jsonstr)
}
string {"id":1,"username":"admin","active":false,"score_set":{"English":100,"Music":90},"operator":["root","tester"]}

StructTag

  • 结构体成员标签Tag,也就是给结构体字段打标签,标签冒号前为类型,冒号后为标签名。
`标签类型:标签名称`
  • 结构体字段标签为-短横线表示不输出到JSON
ProductID int64   `json:"-"` // 表示不进行序列化
  • 结构体字段标签带有自定义名称时,自定义名称会出现在JSON的字段名中。
Name      string  `json:"name"`
  • 结构体字段标签中若带有omitempty选项则当该字段为空或为0时,不会输入到JSON字符串中。
IsOnSale  bool    `json:"is_on_sale,omitempty"`
  • 结构体字段类型为boolstringintint64等,标签中带有,string选项,表示该字段输出到JSON时会自动转换为JSON字符串。
Price     float64 `json:"price,string"`

JSON序列化或反序列化时,结构体类型和目标类型可能不一致,此时可在标签中指定stringnumberboolean三种类型。

package main
import (
    "fmt"
    "encoding/json"
)

func main () {
    user := struct{
        Id int32 `json:"id,string"`
        UserName string `json:"username"`
        Active bool `json:"active"`
        Scores map[string]int32 `json:"score_set"`
        Operator []string `json:"operator"`
        Company string `json:"-"`
        Balance float32 `json:"balace,omitempty"`
    }{
        1,
        "admin",
        false,
        map[string]int32{"Music":90, "English":100},
        []string{"root", "tester"},
        "BOSS Company",
        0.0,
    }

    bs, err := json.MarshalIndent(user, "", "    ")
    jsonstr := string(bs)

    fmt.Printf("%T %v\n", jsonstr, jsonstr)
}
string {
    "id": "1",
    "username": "admin",
    "active": false,
    "score_set": {
        "English": 100,
        "Music": 90
    },
    "operator": [
        "root",
        "tester"
    ]
}

json.MarshalIndent

  • json.MarshalIndex对数据进行JSON格式化时会缩进以方便格式化输出
func json.MarshalIndex(v interface{}, prefix, indent string) ([]byte, error)

例如:

bs, err := json.MarshalIndent(user, "", "    ")
string {
    "id": 1,
    "username": "admin",
    "active": false,
    "score_set": {
        "English": 100,
        "Music": 90
    },
    "operator": [
        "root",
        "tester"
    ]
}

json.Unmarshal

json.Unmarshal()可将JSON格式文本转换为Go语言中预置的数据结构。

func json.Unmarshal(data []byte, v interface{}) error
参数 类型 描述
data []byte JSON格式文本
v interface{} 目标输出容器用于存放解码后的值

JSON解码之前需要在Go中创建一个目标类型的实例用来存放解码后的值,若JSON格式文本能与实例结构对应则解码后的值会被存放到实例中。

Unmarshal()会根据约定的顺序查找目标结构中的字段,若找到即发生匹配。

  1. 从结构体标签中查找包含目标结构的字段
  2. 从结构体字段中查找目标结构的字段
  3. 与目标结构名称相同的字段或除首字母大写外其它字母不区分大小写命令的字段。

如果JSON中字段在Go目标类型中不存在则json.Unmarshal()在解码时会丢弃该字段。

Go语言在解析JSON数据时,需要提前根据JSON的数据结构,预先定义好对应数据类型才能存储解析后的结果。一般会使用结构体与切片来定义用于接收JSON解析后的数据类型。

package main
import (
    "fmt"
    "encoding/json"
)

func main () {
    jsonStr := `{"id":1, "name":"admin", status:true, balance:100.99}`
    jsonBytes := []byte(jsonStr)
 
    type User struct{
        Id int
        Name string
        Status bool
        Balance float64
    }
    user := User{}

    err := json.Unmarshal(jsonBytes, &user)
    fmt.Println(err)//invalid character 's' looking for beginning of object key string
    fmt.Println(user)//{0  false 0}
}

注意JSON格式字符串每个键都必须是字符串

{"id":1, "name":"admin", "status":true, "balance":100.99}

json.Valid

encoding/json包中提供了json.Valid()方法用于验证JSON格式的对象和数组是否正确。

package main
import (
    "fmt"
    "encoding/json"
)


func main () {
    jsonStr := `{"id":1, "name":"admin", "status":true, "balance":100.99}`
    jsonBytes := []byte(jsonStr)
    ok := json.Valid(jsonBytes)
    fmt.Println(ok)//true
    if !ok{
        fmt.Println("json pattern error")
        return
    }
 
    type User struct{
        Id int64
        Name string
        Status bool
        Balance float64
    }
    user := User{}

    err := json.Unmarshal(jsonBytes, &user)
    fmt.Println(err)//
    fmt.Println(user)//{1 admin true 100.99}
}
  • 解码未知结构的JSON数据

Go语言内建灵活的类型系统,其中空接口interface{}是一个通用类型。当需要解码一段未知的JSON格式文本时,只需要将其解码输入出到一个空接口即可。

Go标准库encoding/json允许使用map[string]interface{}[]interface{}类型的值来分别存放未知结构的JSON对象或数组。

定义空接口后,使用json.Unmarshal()会将JSON对象解码到空接口中,最终空接口将会是一个键值对的map[string] interface{}结构。

package main
import (
    "fmt"
    "encoding/json"
)

func main () {

    jsonstr := []byte(`{"id":1, "username":"admin", "status":true, "balance":1000.99}`)
    fmt.Printf("%T\n", jsonstr)//[]uint8
    fmt.Printf("%v\n", jsonstr)//[123 34 105 100 34 58 49 44 32 34 117 115 101 114 110 97 109 101 34 58 34 97 100 109 105 110 34 44 32 34 115 116 97 116 117 115 34 58 116 114 117 101 44 32 34 98 97 108 97 110 99 101 34 58 49 48 48 48 46 57 57 125]

    var v interface{}
    err := json.Unmarshal(jsonstr, &v)
    if err != nil{
        fmt.Printf("error: %v\n", err)
    }

    fmt.Printf("%T\n", v)//map[string]interface {}
    fmt.Printf("%v\n", v)//map[id:1 username:admin status:true balance:1000.99]
}

若需访问解码后的数据结构首先需要判断目标结构是否为预期的数据结构。

//判断目标结构是否为预期的数据结构
data,ok := v.(map[string]interface{})
if !ok{
 fmt.Printf("json decode fail\n")
}
fmt.Printf("%T\n", data)//map[string]interface {}
fmt.Printf("%v\n", data)//map[id:1 username:admin status:true balance:1000.99]

JSON解码过程中元素类型之间的转换关系

JSON Go
布尔值 bool
数值 float64
字符串 string
JSON数组 []interface{}
JSON对象 map[string]interface{}
null nil

通过for循环搭配range语句遍历访问解码后的目标数据

//使用for循环配合range语句遍历访问解码后的目标数据
for k,v := range data{
    switch val := v.(type){
        case string:
            fmt.Println(k, v, val)
        case int:
            fmt.Println(k, v, val)
        case float64:
            fmt.Println(k, v, val)
        case bool:
            fmt.Println(k, v, val)
        case []interface{}:
            fmt.Println(k, v, val)
            for i, iv := range val{
                fmt.Println(i, iv)
            }
        default:
            fmt.Println(k, v, val, "cannot hanle yet")
    }
}

需要注意的是JSON中的整数或小数解码后都会转换为float64浮点数

package main
import (
    "fmt"
    "encoding/json"
    "reflect"
)

func main () {
    type User struct{
        Id int64
        Name string
        Balance float64
    }

    user :=  User{Id:1, Name:"admin", Balance:100.99}
    jsonBytes, _ := json.Marshal(user)
    jsonStr := string(jsonBytes)
    fmt.Println(jsonStr)//{"Id":1,"Name":"admin","Balance":100.99}

    var iuser interface{}
    json.Unmarshal(jsonBytes, &iuser)
    jsonMap := iuser.(map[string]interface{})
    fmt.Println(jsonMap)//map[Id:1 Name:admin Balance:100.99]

    fmt.Printf("Id Type is %s\n", reflect.TypeOf(jsonMap["Id"]).Name())//Id Type is float64
}

json.RawMessage

  • json.RawMessage延迟解析,将JSON字符串以[]byte字节数组存在,使用时才转换为具体类型。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容