Go的json解析 Marshal与Unmarshal


package main
import (
    "encoding/json"
    "fmt"
)
type Stu struct {
    Name  string `json:"name"`
    Age   int
    HIgh  bool
    sex   string
    Class *Class `json:"class"`
}
type Class struct {
    Name  string
    Grade int
}
func main() {//实例化一个数据结构 用于生成json字符串
    stu := Stu{
        Name: "张三",
        Age:  18,
        HIgh: true,
        sex:  "男",
    }
    cla := new(Class) //这个new方法 就相当于 cla := &Class{} 是一个取地址的操作
    cla.Name = "1班"
    cla.Grade = 3
    stu.Class = cla    
    jsonStu, err := json.Marshal(stu)
    if err != nil {//Marshal失败时err!=nil
        fmt.Println("生成json字符串错误")
    }   //jsonStu 是 []byte 类型 转化成string类型便于查看
    fmt.Println(string(jsonStu))
}//{"name":"张三","Age":18,"HIgh":true,"class":{"Name":"1班","Grade":3}}
可导出成员(变量首字母大写) 都可以转成json 因成员变量sex是不可导出的 故无法转成json
变量打上了json标签 如Name旁边的 `json:"name"`  那么转化成的json key就用该标签“name” 否则取变量名作为key 如“Age” “HIgh”
bool类型也 可以直接转换为json的value值
Channel及complex 及函数 不能被编码json字符串
循环的数据结构也不行  会导致marshal 死循环
指针变量 编码时自动转换为它所指向的值 如 cla 变量
不传指针 Stu struct的成员 Class 如果换成 Class struct 类型 效果 是一模一样的 只不过指针更快 且能节省内存空间
json编码成字符串 就是纯粹的字符串了
成员变量是已知的类型 只能接收指定的类型 比如string类型的Name只能赋值string类型的数据
为了通用性 或使代码简洁 希望有一种类型可以接受各种类型的数据 并进行json编码
就用到了interface{}类型 

interface{}类型是个空接口

即没有方法的接口
go 每一种类型都实现了该接口 因此 任何其他类型的数据都可以赋值给interface{}类型
package main
type Stu struct {
    Name  interface{} `json:"name"`
    Age   interface{}
    HIgh  interface{}
    sex   interface{}
    Class interface{} `json:"class"`
}
type Class struct {
    Name  string
    Grade int
}
func main() {//实例化一个数据结构 用于生成json字符串
    stu := Stu{
        Name: "张三",
        Age:  18,
        HIgh: true,
        sex:  "男",
    }    
    cla := new(Class)  //指针变量
    cla.Name = "1班"
    cla.Grade = 3
    stu.Class = cla   
    jsonStu, err := json.Marshal(stu)
    if err != nil {
        fmt.Println("生成json字符串错误")
    }  //jsonStu是[]byte类型 转化成string类型便于查看
    fmt.Println(string(jsonStu))
}
//{"name":"张三","Age":18,"HIgh":true,"class":{"Name":"1班","Grade":3}}
//无论是string int bool 还是指针类型等 都可赋值给interface{}类型 且正常编码 效果与前面的例子一样
在实际项目中 编码成json串的数据结构 往往是切片类型 如下定义了一个[]StuRead 类型的切片
package main
import (
"encoding/json"
"fmt"
)
func main() {
type StuRead struct {
Name  interface{} `json:"name"`
Age   interface{}
HIgh  interface{}
sex   interface{}
Class interface{} `json:"class"`
Test  interface{}
}
var stus1 []*StuRead         //方式1 只声明 不分配内存
stus2 := make([]*StuRead, 0) //方式2 分配初始值为0的内存
//错误示范//new()只能实例化一个 struct 对象 而[]StuRead 是切片 不是对象
//stus := new([]StuRead)
stu1 := &StuRead{"zhangsai", 16, 16, "male", 1, 88}
stu2 := &StuRead{"lisi", 17, 17, "female", 2, 99}
//由方式1和2创建的切片 都能成功追加数据
//方式2最好分配0长度 append时会自动增长 指定初始长度 长度不够时不会自动增长 导致数据丢失
stus1 = append(stus1, stu1) //因为上面stus1是切片类型的结构体指针类型 所以append的类型也必须是取的地址
stus2 = append(stus2, stu2) //因为上面stus2是切片类型的结构体指针类型 所以append的类型也必须是取的地址
json1, _ := json.Marshal(stus1)
json2, _ := json.Marshal(stus2)
fmt.Println(string(json1))
fmt.Println(string(json2))
}
//[{"name":"zhangsai","Age":16,"HIgh":16,"Sex":"male","class":1,"Test":88}]
//[{"name":"lisi","Age":17,"HIgh":17,"Sex":"female","class":2,"Test":99}]

解码时定义对应的切片接受即可
Json Unmarshal 将json字符串解码到相应的数据结构
package main
import (
    "encoding/json"
    "fmt"
)
type StuRead struct {
    Name  interface{} `json:"name"`
    Age   interface{}
    HIgh  interface{}
    sex   interface{}
    Class interface{} `json:"class"`
    Test  interface{}
}
type Class struct {
    Name  string
    Grade int
}
func main() {
    //json字符中 "引号 需用\进行转义 否则编译出错
    //json字符串 对key进行了大小的修改 并添加了sex数据
    data := "{\"name\":\"张三\",\"Age\":18,\"high\":true,\"sex\":\"男\",\"CLASS\":{\"name\":\"1班\",\"Grade\":3}}"
    str := []byte(data)
    //1 Unmarshal 第一个参数是json字符串 第二个参数是接受json解析的数据结构
    //第二个参数必须是指针 否则无法接收解析的数据 如stu仍为空对象 StuRead{}
    //2 可以直接 stu:=new(StuRead) 此时的stu自身就是指针
    stu := StuRead{}
    err := json.Unmarshal(str, &stu)
    if err != nil { //解析失败会报错 如json字符串格式不对 缺"号 缺}等
        fmt.Println(err)
    }
    fmt.Println(stu)
}
//{张三 18 true <nil> map[Grade:3 name:1班] <nil>}

总结 json字符串解析时 需要 接收体 就是 Unmarshal 第二个参数 接受解析后的数据 且 Unmarshal 时接收体必须传递指针 否则解析虽不报错 但数据无法赋值到接受体中 如这里用的是 StuRead{}接收 就无法接收数据
解析时 接收体可自行定义 json串中的key自动在接收体中寻找匹配的项进行赋值

匹配规则

先查找与key一样的json标签 找到则赋值给该标签对应的变量(如Name)
没有json标签的 就从上往下依次查找变量名与key一样的变量 如Age 或者变量名忽略大小写后与key一样的变量 如HIgh Class
第一个匹配的就赋值 后面就算有匹配的也忽略
(前提是该变量必需是可导出的 即首字母大写)
不可导出的变量无法被解析(如 sex 变量 虽然json串中有key为sex的k-v 解析后其值仍为nil 即空值)
当接收体中存在json串中匹配不了的项时 解析会自动忽略该项 该项仍保留原值 如变量Test 保留空值nil

变量 Class 貌似没有解析为 期待样子 因为此时的Class是 interface{}类型的变量 而json串中key为CLASS的value是个复合结构 不是可以直接解析的简单类型数据(如“张三” 18 true等) 所以解析时 由于没有指定变量Class的具体类型 json自动将value为复合结构的数据解析为map[string]interface{}类型的项
也就是说 此时的 struct Class 对象与 StuRead 中的Class变量没有半毛钱关系 故与这次的json解析没有半毛钱关系

看一下这几个interface{}变量解析后的类型
func main() {
    //与前边json解析的代码一致...    
    fmt.Println(stu) //打印json解析前变量类型
    err:=json.Unmarshal(str,&stu)
    fmt.Println("--------------json 解析后-----------")
    fmt.Println(stu) //打印json解析后变量类型   
}
 
//利用反射 打印变量类型
func printType(stu *StuRead){
nameType:=reflect.TypeOf(stu.Name)
ageType:=reflect.TypeOf(stu.Age)
highType:=reflect.TypeOf(stu.HIgh)
sexType:=reflect.TypeOf(stu.sex)
classType:=reflect.TypeOf(stu.Class)
testType:=reflect.TypeOf(stu.Test)
fmt.Println("nameType:",nameType)
fmt.Println("ageType:",ageType)
fmt.Println("highType:",highType)
fmt.Println("sexType:",sexType)
fmt.Println("classType:",classType)
fmt.Println("testType:",testType)
}
//结果
nameType: <nil>
ageType: <nil>
highType: <nil>
sexType: <nil>
classType: <nil>
testType: <nil>
--------------json 解析后-----------
nameType: string
ageType: float64
highType: bool
sexType: <nil>
classType: map[string]interface {}
testType: <nil>
从结果中可见
interface{}类型变量在json解析前 打印出的类型都为nil 就是没有具体类型 这是空接口(interface{}类型)的特点
json解析后 json串中value 只要是 简单数据  都按 默认的类型赋值 如”张三”被赋值成string类型到Name变量中 数字18对应float64 true对应bool类型

简单数据


是指不能再进行二次json解析的数据 如”name”:”张三”只能进行一次json解析

复合数据


类似”CLASS\”:{\”naME\”:\”1班\”,\”GradE\”:3} 这样的数据 是可进行二次甚至多次json解析的 因为它的value也是个可被解析的独立json 即第一次解析key为CLASS的value 第二次解析value中的key为naME和GradE的value
对于”复合数据” 如果接收体中配的项被声明为interface{}类型 go都会默认解析成map[string]interface{}类型 如果想直接解析到struct Class对象中 可以将接受体对应的项定义为该struct类型 如下所示:

type StuRead struct {
...//普通struct类型
Class Class `json:"class"`//指针类型
Class *Class `json:"class"`
}

<span style="font-size: 14px;">// 打印效果</span>Class类型:{张三 18 true <nil> {1班 3} <nil>}
*Class类型:{张三 18 true <nil> 0xc42008a0c0 <nil>}