Go语言使用之JSO使用、源码解析和JSON工具类

在go语言网络编程中,经常会有这样的需求:保存结构体和读取结构体数据。如果你使用redis数据库存储数据,你怎么做?Redis仅支持五种数据类型( String(字符串) 、Hash (哈希)、List(列表)、Set(集合) 和zset(sorted set:有序集合)),不包括结构体,不能直接存储结构体。如果可以转换为五种数据类型之一,问题就简单了。

本篇文章分析,如何使用JSON处理数据。

一、JSON 基本介绍

  1. JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。

  2. 易于人阅读和编写。同时也易于机器解析和生成。 JSON是在2001年开始推广使用的数据格式,目前已经成为主流的数据格式。

  3. JSON易于机器解析和生成,并有效地提升网络传输效率,通常程序在网络传输时会先将数据(结构体、map等)序列化成json字符串,到接收方得到json字符串时,在反序列化恢复成原来的数据类型(结构体、map等) 。这种方式已然成为各个语言的标准

这里写图片描述

二、JSON数据格式说明

任何的数据类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组, map, 结构体等.
JSON 键值对是用来保存 数据一种方式,键/值对组合中的键名写在前面并用双引号 “” 包裹,使用冒号 : 分隔,然后紧接着值.

如下示例:

[{"key1":val1, "key2":val2, "key3" : val3, "key4":[val4, val5]},
{"key1":val1, "key2":val2, "key3" : val3, "key4":[val4, val5]}]
{"firstName": "Json"}
{"name": "tom", "age": 18, "address":["北京", "上海"]}
[{"name": "tom", "age": 18, "address":["北京", "上海"]},
{"name": "mary", "age": 28, "address":["广州", "深圳"]}]

三、JSON解析工具:

线上网站推荐:https://www.json.cn/ 网站可以验证一个json格式的数据是否正确
线下工具:HiJson 2.1.2_jdk64

四、json序列化go中数据

1、介绍

json序列化是指,将有key-value结构的数据类型(比如结构体、map、切片)序列化成json字符串的操作。

2、源码语法

2.1 type Marshaler

type Marshaler interface {
    MarshalJSON() ([]byte, error)
}
实现了Marshaler接口的类型可以将自身序列化为合法的json描述。

2.2 func Marshal

func Marshal(v interface{}) ([]byte, error)
Marshal函数返回v的json编码。

Marshal函数会递归的处理值。如果一个值实现了Marshaler接口切非nil指针,会调用其MarshalJSON方法来生成json编码。nil指针异常并不是严格必需的,但会模拟与UnmarshalJSON的行为类似的必需的异常。

否则,Marshal函数使用下面的基于类型的默认编码格式:

布尔类型编码为json布尔类型。

浮点数、整数和Number类型的值编码为json数字类型。

字符串编码为json字符串。角括号"<"">"会转义为"\u003c""\u003e"以避免某些浏览器吧json输出错误理解为HTML。基于同样的原因,"&"转义为"\u0026"。

数组和切片类型的值编码为json数组,但[]byte编码为base64编码字符串,nil切片编码为null。

结构体的值编码为json对象。每一个导出字段变成该对象的一个成员,除非:

- 字段的标签是"-"
- 字段是空值,而其标签指定了omitempty选项
空值是false、0""nil指针、nil接口、长度为0的数组、切片、映射。对象默认键字符串是结构体的字段名,但可以在结构体字段的标签里指定。结构体标签值里的"json"键为键名,后跟可选的逗号和选项,举例如下:

// 字段被本包忽略
Field int `json:"-"`
// 字段在json里的键为"myName"
Field int `json:"myName"`
// 字段在json里的键为"myName"且如果字段为空值将在对象中省略掉
Field int `json:"myName,omitempty"`
// 字段在json里的键为"Field"(默认值),但如果字段为空值会跳过;注意前导的逗号
Field int `json:",omitempty"`
"string"选项标记一个字段在编码json时应编码为字符串。它只适用于字符串、浮点数、整数类型的字段。这个额外水平的编码选项有时候会用于和javascript程序交互:

Int64String int64 `json:",string"`
如果键名是只含有unicode字符、数字、美元符号、百分号、连字符、下划线和斜杠的非空字符串,将使用它代替字段名。

匿名的结构体字段一般序列化为他们内部的导出字段就好像位于外层结构体中一样。如果一个匿名结构体字段的标签给其提供了键名,则会使用键名代替字段名,而不视为匿名。

Go结构体字段的可视性规则用于供json决定那个字段应该序列化或反序列化时是经过修正了的。如果同一层次有多个(匿名)字段且该层次是最小嵌套的(嵌套层次则使用默认go规则),会应用如下额外规则:

1)json标签为"-"的匿名字段强行忽略,不作考虑;

2)json标签提供了键名的匿名字段,视为非匿名字段;

3)其余字段中如果只有一个匿名字段,则使用该字段;

4)其余字段中如果有多个匿名字段,但压平后不会出现冲突,所有匿名字段压平;

5)其余字段中如果有多个匿名字段,但压平后出现冲突,全部忽略,不产生错误。

对匿名结构体字段的管理是从go1.1开始的,在之前的版本,匿名字段会直接忽略掉。

映射类型的值编码为json对象。映射的键必须是字符串,对象的键直接使用映射的键。

指针类型的值编码为其指向的值(的json编码)。nil指针编码为null。

接口类型的值编码为接口内保持的具体类型的值(的json编码)。nil接口编码为null。

通道、复数、函数类型的值不能编码进json。尝试编码它们会导致Marshal函数返回UnsupportedTypeError。

Json不能表示循环的数据结构,将一个循环的结构提供给Marshal函数会导致无休止的循环。

3、快速入门案例

这里只介绍一下常用的数据类型,结构体、map和切片的序列化,其它数据类型的序列化类似。

代码示例:

package utils

import (
    "fmt"
    "encoding/json"
)
//定义一个结构体
//如果需要对结构体进行序列化,则字段名需要大写.
type Monster struct {
    Name  string
    Age int
    Skill string  
}


//结构体序列化
func TestStruct() {

    //先创建要给结构体实例
    monster1 :=  Monster{
        Name : "红孩儿~",
        Age : 8,
        Skill : "吐火",
    }
    fmt.Println("源串 = ",&monster1)
    //序列化
    data, err := json.Marshal(&monster1)
    if err != nil {
        fmt.Println("序列化失败 err=", err)
        return
    }
    fmt.Println("序列化的字符串 = ",string(data))

}

//map序列化
func TestMap() {

    var myMap map[string]interface{} // map的key是string而值 interface{}
    myMap = make(map[string]interface{}, 1)
    myMap["Name"] = "牛魔王"
    myMap["Age"] = 500
    myMap["lover"] = [2]string{"铁扇公主", "玉面狐狸"}
    myMap["Skill"] = "牛魔拳~"

    //序列化
    data, err := json.Marshal(myMap)
    if err != nil {
        fmt.Println("序列化失败 err=", err)
        return
    }
    fmt.Println("序列化的字符串 = ",string(data))


}

//Slice序列化
func TestSlice(){

    var mySlice []map[string]interface{}

    var myMap map[string]interface{} // map的key是string而值 interface{}
    myMap = make(map[string]interface{}, 1)
    myMap["Name"] = "牛魔王"
    myMap["Age"] = 500
    myMap["lover"] = [2]string{"铁扇公主", "玉面狐狸"}
    myMap["Skill"] = "牛魔拳~"

    mySlice = append(mySlice, myMap)

    var myMap2 map[string]interface{} // map的key是string而值 interface{}
    myMap2 = make(map[string]interface{}, 1)
    myMap2["Name"] = "金角大王"
    myMap2["Age"] = 300
    myMap2["lover"] = [2]string{"银角大王", "铜脚大王"}
    myMap2["Skill"] = "晃金绳~"

    mySlice = append(mySlice, myMap2)

    //序列化
    data, err := json.Marshal(mySlice)
    if err != nil {
        fmt.Println("序列化失败 err=", err)
        return
    }
    fmt.Println("序列化的字符串 = ",string(data))


}

//Float序列化
func TestFloat() {
    var num1 float64 = 908.12


    //序列化
    data, err := json.Marshal(num1)
    if err != nil {
        fmt.Println("序列化失败 err=", err)
        return
    }
    fmt.Println("序列化的字符串 = ",string(data))

}


测试结果:

源串 =  &{红孩儿~ 8 吐火}
序列化的字符串 =  {"Name":"红孩儿~","Age":8,"Skill":"吐火"}
序列化的字符串 =  {"Age":500,"Name":"牛魔王","Skill":"牛魔拳~","lover":["铁扇公主","玉面狐狸"]}
序列化的字符串 =  [{"Age":500,"Name":"牛魔王","Skill":"牛魔拳~","lover":["铁扇公主","玉面狐狸"]},{"Age":300,"Name":"金角大王","Skill":"晃金绳~","lover":["银角大王","铜脚大王"]}]
序列化的字符串 =  908.12

4、json序列化注意事项

对于结构体的序列化,如果我们希望序列化后的key的名字,我们自己重新制定,那么可以给struct指定一个tag标签

五、json反序列化go中数据

1、 介绍

json反序列化是指,将json字符串反序列化成对应的数据类型(比如结构体、map、切片)的操作。

2、源码语法

2.1 type Unmarshaler

type Unmarshaler interface {
    UnmarshalJSON([]byte) error
}
实现了Unmarshaler接口的对象可以将自身的json描述反序列化。该方法可以认为输入是合法的json字符串。如果要在方法返回后保存自身的json数据,必须进行拷贝。

2.2 func Unmarshal

func Unmarshal(data []byte, v interface{}) error
Unmarshal函数解析json编码的数据并将结果存入v指向的值。

Unmarshal和Marshal做相反的操作,必要时申请映射、切片或指针,有如下的附加规则:

要将json数据解码写入一个指针,Unmarshal函数首先处理json数据是json字面值null的情况。此时,函数将指针设为nil;否则,函数将json数据解码写入指针指向的值;如果指针本身是nil,函数会先申请一个值并使指针指向它。

要将json数据解码写入一个结构体,函数会匹配输入对象的键和Marshal使用的键(结构体字段名或者它的标签指定的键名),优先选择精确的匹配,但也接受大小写不敏感的匹配。

要将json数据解码写入一个接口类型值,函数会将数据解码为如下类型写入接口:

Bool                   对应JSON布尔类型
float64                对应JSON数字类型
string                 对应JSON字符串类型
[]interface{}          对应JSON数组
map[string]interface{} 对应JSON对象
nil                    对应JSON的null
如果一个JSON值不匹配给出的目标类型,或者如果一个json数字写入目标类型时溢出,Unmarshal函数会跳过该字段并尽量完成其余的解码操作。如果没有出现更加严重的错误,本函数会返回一个描述第一个此类错误的详细信息的UnmarshalTypeError。

JSON的null值解码为go的接口、指针、切片时会将它们设为nil,因为null在json里一般表示“不存在”。 解码json的null值到其他go类型时,不会造成任何改变,也不会产生错误。

当解码字符串时,不合法的utf-8或utf-16代理(字符)对不视为错误,而是将非法字符替换为unicode字符U+FFFD。

3、快速入门案例

这里只介绍一下常用的数据类型,结构体、map和切片的序列化,其它数据类型的序列化类似。

代码示例:

package utils

import (
    "fmt"
    "encoding/json"
)
//定义一个结构体
//如果需要对结构体进行序列化,则字段名需要大写.
type Monster struct {
    Name  string
    Age int
    Skill string  
}


/反序列化struct
func UnMarshalStruct() {

    str := `{"Name":"红孩儿","Age":8,"Skill":"吐火"}`

    //定义一个结构体
    var monster Monster
    err := json.Unmarshal([]byte(str), &monster)
    if err != nil {
        fmt.Println("unmarshal err=", err)
    }

    fmt.Printf("反序列化struct: monster=%T, monster=%v\n", monster, monster)


}

//反序列化map
func UnMarshalMap() {

    str := `{"Age":500,"Name":"牛魔王","Skill":"牛魔拳~","lover":["铁扇公主","玉面狐狸"]}`

    var myMap map[string]interface{}

    //反序列化,不需要对 myMap 对其make ,因为 json.Unmarshal会make

    err := json.Unmarshal([]byte(str), &myMap)
    if err != nil {
        fmt.Println("unmarshal err=", err)
    }

    fmt.Printf("反序列化map: myMap=%T, myMap=%v\n", myMap, myMap)
}

//反序列化slice
func UnMarshalSlice() {

    str := `[{"Age":500,"Name":"牛魔王","Skill":"牛魔拳~","lover":["铁扇公主","玉面狐狸"]},{"Age":300,"Name":"金角大王","Skill":"晃金绳~","lover":["银角大王","铜脚大王"]}]`

    var mySlice []map[string]interface{}

    //反序列化,不需要对 mySlice 对其make ,因为 json.Unmarshal会make

    err := json.Unmarshal([]byte(str), &mySlice)
    if err != nil {
        fmt.Println("unmarshal err=", err)
    }

    fmt.Printf("反序列化slice: mySlice=%T, mySlice=%v\n", mySlice, mySlice)

}





测试结果:

反序列化struct: monster=utils.Monster, monster={红孩儿 8 吐火}
反序列化map: myMap=map[string]interface {}, myMap=map[Age:500 Name:牛魔王 Skill:牛魔拳~ lover:[铁扇公主 玉面狐狸]]
反序列化slice: mySlice=[]map[string]interface {}, mySlice=[map[Age:500 Name:牛魔王 Skill:牛魔拳~ lover:[铁扇公主 玉面狐狸]] map[Skill:晃金绳~ lover:[银角大王 铜脚大王] Age:300 Name:金角大王]]

六、封装的工具类

//序列化数据
func MarshalData(v interface{}) (data []byte, err error) {
    data, err = json.Marshal(v)
    return
}

//反序列化数据
func UnmarshalData(b []byte,v interface{}) (data interface{}, err error)  {
    err = json.Unmarshal(b, v)
    data = v
    return
}

猜你喜欢

转载自blog.csdn.net/TDCQZD/article/details/81395357