zoukankan      html  css  js  c++  java
  • Golang操作Json

    基本的序列化

    首先我们来看看Go语言中json.Marshal()(序列化)与json.Unmarshal(反序列化)的基本用法。

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    )
    
    type Person struct{
    	Name string
    	Age int64
    	Weight float64
    }
    
    func main() {
    	p1 := Person{
    		Name:"Negan",
    		Age: 68,
    		Weight: 140.5,
    	}
    
    	// struct -> json string
    	b, err := json.Marshal(p1)
    	if err != nil{
    		fmt.Printf("json.Marshal failed, err:%v
    ",err)
    		return
    	}
    	fmt.Printf("str:%s
    ",b)  // str:{"Name":"Negan","Age":68,"Weight":140.5}
    
    	// json string -> struct
    	var p2 Person
    	err = json.Unmarshal(b, &p2)
    	if err != nil{
    		fmt.Printf("json.Unmarshal failed, err:%v
    ", err)
    		return
    	}
    	fmt.Printf("p2:%#v
    ", p2)
    }
    

    输出:

    str:{"Name":"Negan","Age":68,"Weight":140.5}
    p2:main.Person{Name:"Negan", Age:68, Weight:140.5}
    

    结构体tag介绍

    Tag是结构体的原信息,可以在运行的时候通过反射的机制读取出来,Tag在结构体的后方定义,由一对反引号包裹起来,具体格式如下:

    `key1:"value1 key2:"value2"`
    

    结构体tag由一个或多个键值对组成,键与值使用冒号分隔,值使用双引号括起来,同一结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。

    使用json tag指定字段名

    序列化与反序列化默认情况下使用结构体的字段名,我们可以通过给结构体字段添加tag来指定json序列生成的字段名。

    type Person struct{
        Name string `json:"name"`  // 指定json序列化/反序列化时使用小写name
        Age int64
        Weight float64
    }
    

    忽略某个字段

    如果现在json序列化/反序列化的时候忽略掉结构体中的某个字段,可以按如下方式在tag中添加-

    type Person struct{
        Name string `json:"name"`  // 指定json序列化/反序列化时使用小写
        Age int64
        Weight float64 `json:"-"`  // 指定json序列化/反序列化时忽略此字段
    }
    

    忽略空值字段

    当struct中的子弹没有值时,json.Marshal()序列化的时候不会忽略这些字段,而是默认输出字段类型的零值,如intfloat类型零值都是0,string类型的零值是"",对象类型的零知识nil。如果想要在序列化时忽略这些没有值的字段时,可以在对应字段添加omitemptytag。

    示例:

    type User struct {
    	Name string `json:"name"`
    	Email string `json:"email"`
    	Hobby []string `json:"hobby"`
    }
    
    func main() {
    	u1 := User{
    		Name: "Negan",
    	}
    	// struct -> json.string
    	b, err := json.Marshal(u1)
    	if err != nil{
    		fmt.Printf("json Marshal failed,err:%v
    ",err)
    		return
    	}
    	fmt.Printf("str:%s
    ", b)  // str:{"name":"Negan","email":"","hobby":null}
    }
    

    如果想要在最终的序列化结果中去掉空值字段,可以像下面这样定义结构体:

    // 在tag中添加omitempty忽略空值
    // 注意这里hobby,omitempty合起来是json tag值,中间用英文逗号分隔
    type User struct {
    	Name string `json:"name"`
    	Email string `json:"email,omitempty"`
    	Hobby []string `json:"hobby,omitempty"`
    }
    
    func main() {
    	u1 := User{
    		Name: "Negan",
    	}
    	// struct -> json.string
    	b, err := json.Marshal(u1)
    	if err != nil{
    		fmt.Printf("json Marshal failed,err:%v
    ",err)
    		return
    	}
    	fmt.Printf("str:%s
    ", b)  // str:{"name":"Negan"}
    }
    

    忽略嵌套结构体控制字段

    首先来看几种结构体嵌套的示例:

    type User struct{
    	Name string `json:"name"`
    	Email string `json:"email,omitempty"`
    	Hobby []string `json:"hobby,omitempty"`
    	Profile
    }
    
    type Profile struct{
    	Website string `json:"site"`
    	Slogan string `json:"slogan"`
    }
    
    func main() {
    	u1 := User{
    		Name:"Negan",
    		Hobby: []string{"女人","棒球"},
    	}
    	b, err := json.Marshal(u1)
    	if err != nil{
    		fmt.Printf("json.Marshal failed,err:%v
    ",err)
    		return
    	}
    	fmt.Printf("str:%s
    ",b)  // str:{"name":"Negan","hobby":["女人","棒球"],"site":"","slogan":""}
    }
    

    匿名嵌套Profile时序列化后的json串为单层的,想要变成嵌套的json串,需要改为具名嵌套或定义字段tag。

    type User struct{
    	Name string `json:"name"`
    	Email string `json:"email,omitempty"`
    	Hobby []string `json:"hobby,omitempty"`
    	// Profile Profile
    	Profile `json:"profile"`
    }
    
    type Profile struct{
    	Website string `json:"site"`
    	Slogan string `json:"slogan"`
    }
    
    func main() {
    	u1 := User{
    		Name:"Negan",
    		Hobby: []string{"女人","棒球"},
    	}
    	b, err := json.Marshal(u1)
    	if err != nil{
    		fmt.Printf("json.Marshal failed,err:%v
    ",err)
    		return
    	}
    	fmt.Printf("str:%s
    ",b)  //str:{"name":"Negan","hobby":["女人","棒球"],"profile":{"site":"","slogan":""}}
    
    }
    

    想要在嵌套结构体为空值是,忽略该字段,仅添加omitempty是不够的:

    type User struct{
    	Name string `json:"name"`
    	Email string `json:"email,omitempty"`
    	Hobby []string `json:"hobby,omitempty"`
    	// Profile Profile
    	Profile `json:"profile,omitempty"`
    }
    
    type Profile struct{
    	Website string `json:"site"`
    	Slogan string `json:"slogan"`
    }
    
    func main() {
    	u1 := User{
    		Name:"Negan",
    		Hobby: []string{"女人","棒球"},
    	}
    	b, err := json.Marshal(u1)
    	if err != nil{
    		fmt.Printf("json.Marshal failed,err:%v
    ",err)
    		return
    	}
    	fmt.Printf("str:%s
    ",b)  //str:{"name":"Negan","hobby":["女人","棒球"],"profile":{"site":"","slogan":""}}
    }
    

    需要使用嵌套的结构体指针:

    type User struct{
    	Name string `json:"name"`
    	Email string `json:"email,omitempty"`
    	Hobby []string `json:"hobby,omitempty"`
    	// Profile Profile
    	*Profile `json:"profile,omitempty"`
    }
    
    type Profile struct{
    	Website string `json:"site"`
    	Slogan string `json:"slogan"`
    }
    
    func main() {
    	u1 := User{
    		Name:"Negan",
    		Hobby: []string{"女人","棒球"},
    	}
    	b, err := json.Marshal(u1)
    	if err != nil{
    		fmt.Printf("json.Marshal failed,err:%v
    ",err)
    		return
    	}
    	fmt.Printf("str:%s
    ",b)  // str:{"name":"Negan","hobby":["女人","棒球"]}
    }
    

    不修改原结构体忽略空值字段

    我们需要json序列化User,但是不想把密码也序列化了,又不想修改User结构体,这个时候我们就可以使用创建另外一个结构体PublicUser匿名嵌套原User,同时制定Password字段为匿名结构体指针类型,并添加omitemptytag。

    type User struct {
    	Name string `json:"name"`
    	Password string `json:"password"`
    }
    
    type PublicUser struct {
    	*User  // 匿名嵌套
    	Password *struct{} `json:"password,omitempty"`
    }
    
    func main() {
    	u1 := User{
    		Name:"Negan",
    		Password: "123456",
    	}
    	b,err := json.Marshal(PublicUser{User:&u1})
    	if err != nil{
    		fmt.Printf("JSON.Marshal u1 failed, err:%v
    ",err)
    		return
    	}
    	fmt.Printf("str:%s
    ",b)  // str:{"name":"Negan"}
    }
    

    优雅处理字符串格式的数字

    有时候前端在传递来的json数据中可能会使用字符串类型的数字,这个时候可以在结构体tag中添加string来告诉json包从字符串中解析相应字段的数据。

    type Card struct{
    	ID int64 `json:",string"`  // 添加string tag
    	Score float64 `json:"score,string"`  // 添加string tag
    }
    
    func main() {
    	jsonStr := `{"id":"123456","score":"88.5"}`
    	var c1 Card
    	if err := json.Unmarshal([]byte(jsonStr),&c1);err!=nil{
    		fmt.Printf("json.Unmarsha jsonStr1 failed,err:%v
    ", err)
    		return 
    	}
    	fmt.Printf("c1:%#v
    ",c1)  // c1:main.Card{ID:123456, Score:88.5}
    }
    

    整数变浮点数

    在Json协议中是没有整型和浮点型之分的,它们统称为number,json字符串中的数字经过Go语言中的json包反序列化之后会成为float64类型。下面的代码便延时了这个问题:

    func main() {
    	// map[string]interface{} ->json string
    	var m = make(map[string]interface{},1)
    	m["count"] = 1 // int
    	b,err := json.Marshal(m)
    	if err != nil{
    		fmt.Printf("marshal failed, err:%v
    ",err)
    	}
    	fmt.Printf("str:%#v
    ",string(b))
    
    	// json string -> map[string]interface{}
    	var m2 map[string]interface{}
    	err = json.Unmarshal(b,&m2)
    	if err != nil{
    		fmt.Printf("unmarshal failed,err:%v
    ",err)
    		return
    	}
    	fmt.Printf("value:%v
    ",m2["count"])  // 1
    	fmt.Printf("type:%T
    ",m2["count"])   // float64
    }
    

    这种场景下如果想要更合理的处理数字就需要使用decoder去反序列化,示例代码如下:

    func main() {
    	// map[string]interface{} -> json string
    	var m = make(map[string]interface{},1)
    	m["count"] = 1 // int
    	b, err := json.Marshal(m)
    	if err != nil{
    		fmt.Printf("marshal failed,err:%v
    ",err)
    	}
    	fmt.Printf("str:%#v
    ",string(b))
    
    	// json string -> map[string]interface{}
    	var m2 map[string]interface{}
    	// 使用decoder方式反序列化,指定使用number类型
    	decoder := json.NewDecoder(bytes.NewReader(b))
    	decoder.UseNumber()
    	err = decoder.Decode(&m2)
    	if err != nil{
    		fmt.Printf("unmarshal failed,err:%v
    ",err)
    		return
    	}
    	fmt.Printf("value:%v
    ",m2["count"])  // 1
    	fmt.Printf("type:%T
    ",m2["count"])  // json.Number
    	// 将m2["count"]转为json.Number之后调用Int64()方法获得int64类型的值
    	count,err := m2["count"].(json.Number).Int64()
    	if err != nil{
    		fmt.Printf("parse to int64 failed, err:%v
    ",err)
    		return
    	}
    	fmt.Printf("type:%T
    ",int(count))  // int
    }
    

    json.Number的源码定义如下:

    // A Number represents a JSON number literal.
    type Number string
    
    // String returns the literal text of the number.
    func (n Number) String() string { return string(n) }
    
    // Float64 returns the number as a float64.
    func (n Number) Float64() (float64, error) {
    	return strconv.ParseFloat(string(n), 64)
    }
    
    // Int64 returns the number as an int64.
    func (n Number) Int64() (int64, error) {
    	return strconv.ParseInt(string(n), 10, 64)
    }
    

    我们在处理number类型的json字段时需要先得到json.Number类型,然后根据该字段的实际类型调用Float()Int64()

    自定义解析时间字段

    Go语言内置的json包使用RFC3339标准中定义的时间格式,对我们序列化时间字段的时候有很多限制。

    func timeFieldDemo(){
    	p1 := Post{
    		CreateTime: time.Now(),
    	}
    	b,err := json.Marshal(p1)
    	if err != nil{
    		fmt.Printf("json.Marshal p1 failed,err:%v
    ",err)
    		return
    	}
    	fmt.Printf("str:%s
    ",b)
    
    	jsonStr := `{"create_time":"2020-05-23 10:49:50"}`
    	var p2 Post
    	if err := json.Unmarshal([]byte(jsonStr),&p2); err != nil{
    		fmt.Printf("json.Unmarshal failed,err:%v
    ",err)
    		return
    	}
    	fmt.Printf("p2:%#v
    ",p2)
    }
    

    上面代码运行结果如下:

    str:{"create_time":"2020-05-23T10:52:56.8148613+08:00"}
    json.Unmarshal failed,err:parsing time ""2020-05-23 10:49:50"" as ""2006-01-02T15:04:05Z07:00"":
    cannot parse " 10:49:50"" as "T"
    

    也就是说内置的json包不识别我们常用的字符串时间格式,如2020-05-23 10:49:50

    不过我们可以通过实现json.Marshaler/json.Unmarshaler接口实现自定义的时间格式解析。

    type CustomTime struct{
    	time.Time
    }
    
    var ctLayout = "2006-01-02 15:04:05"
    
    var nilTime = (time.Time{}).UnixNano()
    
    func (c *CustomTime) UnmarshalJSON(b []byte)(err error){
    	s := strings.Trim(string(b),""")
    	if s == "null"{
    		c.Time = time.Time{}
    		return
    	}
    	c.Time,err = time.Parse(ctLayout,s)
    	return
    }
    
    
    func (c *CustomTime) MarshalJSON()([]byte,error){
    	if c.Time.UnixNano() == nilTime{
    		return []byte("null"),nil
    	}
    	return []byte(fmt.Sprintf(""%s"",c.Time.Format(ctLayout))),nil
    }
    
    func (c *CustomTime) IsSet() bool {
    	return c.UnixNano() != nilTime
    }
    
    type Post struct {
    	CreateTime CustomTime `json:"create_time"`
    }
    
    func timeFiledDemo(){
    	p1 := Post{CreateTime: CustomTime{time.Now()}}
    	b,err := json.Marshal(p1)
    	if err != nil{
    		fmt.Printf("json.Marshal p1 failed, err:%v
    ",err)
    		return
    	}
    	fmt.Printf("str:%s
    ",b)
    
    	jsonStr := `{"create_time":"2020-05-23 15:51:51"}`
    	var p2 Post
    	if err := json.Unmarshal([]byte(jsonStr),&p2);err !=nil{
    		fmt.Printf("json.Unmarshal failed,err:%v
    ",err)
    		return
    	}
    	fmt.Printf("p2:%#v
    ",p2)
    }
    

    自定义MarshalJSON和UnmarshalJSON方法

    上面的那种自定义类型的方式稍显啰嗦,下面来看一种相对边界的方法。

    首先需要知道的是,如果能够为某个类型实现了MarshalJSON()([]byte,error)UnmarshalJSON(b byte[])error方法,那么这个类型在序列化(MarshalJSON)/反序列化(UnmarshalJSON)时就会使用定制的相应的方法。

    type Order struct{
    	ID int `json:"id"`
    	Title string `json:"title"`
    	CreatedTime time.Time `json:"created_time"`
    }
    
    const layout = "2006-01-02 15:04:05"
    
    // MarshalJSON 为Orderl类型实现自定义的MarshalJSON方法
    func (o *Order) MarshalJSON()([]byte, error){
    	type TempOrder Order // 定义与Order字段一致的新类型
    	return json.Marshal(struct{
    		CreatedTime string `json:"created_time"`
    		* TempOrder // 避免直接签到Order进入死循环
    	}{
    		CreatedTime:o.CreatedTime.Format(layout),
    		TempOrder:(*TempOrder)(o),
    	})
    }
    
    // UnmarshalJSON 为Order类型实现自定义的UnmarshalJson方法
    func (o *Order) UnmarshalJSON(data []byte)error{
    	type TempOrder Order  // 定义与Order字段一致的新类型
    	ot := struct{
    		CreatedTime string `json:"created_time"`
    		*TempOrder  // 避免直接嵌套Order进入死循环
    	}{
    		TempOrder:(*TempOrder)(o),
    	}
    	if err := json.Unmarshal(data,&ot); err != nil{
    		return err
    	}
    	var err error
    	o.CreatedTime,err = time.Parse(layout,ot.CreatedTime)
    	if err != nil{
    		return err
    	}
    	return nil
    }
    
    // 自定义序列化方法
    func cuntomMethodDemo(){
    	o1 := Order{
    		ID: 123456,
    		Title: "《梵高先生》",
    		CreatedTime:time.Now(),
    	}
    
    	// 通过自定义的MarshalJSON方法实现struct -> json string
    	b,err := json.Marshal(&o1)
    	if err != nil{
    		fmt.Printf("json.Marshal o1 failed,err:%v
    ",err)
    		return
    	}
    	fmt.Printf("str:%s
    ",b)
    
    	// 通过自定义的UnmarshalJSON
    	jsonStr := `{"created_time":"2020-05-23 16:36:20", "id":1234545,"title":"《山阴路的夏天》"}`
    	var o2 Order
    	if err := json.Unmarshal([]byte(jsonStr),&o2);err!=nil{
    		fmt.Printf("json.Unmarshal failed, err:%v
    ",err)
    		return
    	}
    	fmt.Printf("o2:%#v
    ",o2)
    }
    

    输出结果:

    str:{"CreatedTime":"2020-05-23 16:41:02","id":123456,"title":"《梵高先生》","created_time":"2020-
    05-23T16:41:02.5101073+08:00"}
    o2:main.Order{ID:1234545, Title:"《山阴路的夏天》", CreatedTime:time.Time{wall:0x0, ext:637258485
    80, loc:(*time.Location)(nil)}}
    
    

    使用匿名结构体添加字段

    使用内嵌结构体能够扩展结构体的字段,但是有时候我们没有必要淡出定义新的结构体,可以使用匿名结构体简化操作:

    func StructDemo(){
    	u1 := UserInfo{
    		ID: 123456,
    		Name: "李大鹅",
    	}
    
    	// 使用匿名结构体内嵌User并添加额外字段Token
    	b, err := json.Marshal(struct {
    		*UserInfo
    		Token string `json:"token"`
    	}{
    		&u1,
    		"91je3adkljdafa",
    	})
    	if err != nil{
    		fmt.Printf("json.Marsha failed, err:%v
    ",err)
    		return
    	}
    	fmt.Printf("str:%s
    ",b)  // str:{"id":123456,"name":"李大鹅","token":"91je3adkljdafa"}
    }
    

    使用匿名结构体组合多个结构体

    同理,也可以使用匿名结构体组合多个结构体来序列化与反序列化数据:

    type Comment struct{
    	Content string
    }
    
    type User struct {
    	Name string `json:"name"`
    	Age int `json:"age"`
    }
    
    func StructDemo(){
    	c1 := Comment{
    		Content: "永远不要高估自己",
    	}
    	u1 := User{
    		Name: "李大鹅",
    		Age: 28,
    	}
    
    	// struct -> json string
    	b,err := json.Marshal(struct {
    		*Comment
    		*User
    	}{&c1,&u1})
    	if err != nil{
    		fmt.Printf("json.Marshal failed, err:%v
    ",err)
    		return
    	}
    	fmt.Printf("str:%s
    ",b)
    
    	// json string -> struct
    	jsonStr := `{"Content":"永远不要高估自己","name":"李大鹅","age":28}`
    	var (
    		c2 Comment
    		u2 User
    	)
    	if err := json.Unmarshal([]byte(jsonStr),&struct {
    		*Comment
    		* User
    	}{&c2,&u2});err != nil{
    		fmt.Printf("json.Unmarshal failed,err:%v
    ",err)
    		return
    	}
    	fmt.Printf("c2:%#v u2:%#v
    ",c2,u2)
    }
    

    输出结果:

    str:{"Content":"永远不要高估自己","name":"李大鹅","age":28}
    c2:main.Comment{Content:"永远不要高估自己"} u2:main.User{Name:"李大鹅", Age:28}
    

    处理不确定层级的json

    如果json串没有固定的格式导致不好定义与其相对应的结构体时,我们可以使用json.RawMessage原始字节数据保存下来。

    type sendMsg struct {
    	User string `json:"user"`
    	Msg string `json:"msg"`
    }
    
    func rawMessageDemo(){
    	jsonStr := `{"sendMsg":{"user":"李大鹅","msg":"永远不要高估自己"},"say":"hello"}`
    	// 定义一个map,value类型为json.RawMessage,方便后续更灵活地处理
    	var data map[string]json.RawMessage
    	if err := json.Unmarshal([]byte(jsonStr),&data);err != nil{
    		fmt.Printf("json.Unmarshal jsonStr failed,err:%v
    ",err)
    		return
    	}
    	var msg sendMsg
    	if err := json.Unmarshal(data["sendMsg"],&msg);err!=nil{
    		fmt.Printf("json.Unmarshal failed,err:%v
    ",err)
    		return
    	}
    	fmt.Printf("msg:%#v
    ",msg) // msg:main.sendMsg{User:"李大鹅", Msg:"永远不要高估自己"}
    }
    
  • 相关阅读:
    [Reinforcement Learning] Cross-entropy Method
    [Deep Learning] 正则化
    [Deep Learning] 常用的Active functions & Optimizers
    [Machine Learning] 浅谈LR算法的Cost Function
    [Deep Learning] 深度学习中消失的梯度
    [Machine Learning] logistic函数和softmax函数
    [Deep Learning] 神经网络基础
    [Machine Learning] Active Learning
    [Machine Learning & Algorithm]CAML机器学习系列2:深入浅出ML之Entropy-Based家族
    [Machine Learning & Algorithm]CAML机器学习系列1:深入浅出ML之Regression家族
  • 原文地址:https://www.cnblogs.com/huiyichanmian/p/12955230.html
Copyright © 2011-2022 走看看