Basic serialization:
First, let's take a look at the basic usage of json.Marshal()
(serialization) and json.Unmarshal
(deserialization) in Go language .
type Person struct { Name string Age int64 Weight float64 } func main() { p1 := Person{ Name: "七米", Age: 18, Weight: 71.5, } // struct -> json string b, err := json.Marshal(p1) if err != nil { fmt.Printf("json.Marshal failed, err:%v\n", err) return } fmt.Printf("str:%s\n", b) // json string -> struct var p2 Person err = json.Unmarshal(b, &p2) if err != nil { fmt.Printf("json.Unmarshal failed, err:%v\n", err) return } fmt.Printf("p2:%#v\n", p2) }
Output:
str:{"Name":"七米","Age":18,"Weight":71.5} p2:main.Person{Name:"七米", Age:18, Weight:71.5}
Structure tag introduction:
Tag
It is the meta information of the structure, which can be read through the reflection mechanism at runtime. Tag
It is defined behind the structure field and is wrapped by a pair of backticks . The specific format is as follows:
`key1:"value1" key2:"value2"`
The structure tag consists of one or more key-value pairs. Keys and values are separated by colons , and values are enclosed in double quotes . Multiple key-value pair tags can be set in the same structure field, and different key-value pairs are separated by spaces .
Use json tag to specify the field name:
Serialization and deserialization use the field names of the structure by default. We can specify the field names generated by json serialization by adding tags to the structure fields:
// Use json tag to specify the behavior when serializing and deserializing. Type Person struct { Name string `json:" name "` // Specify the lowercase name when using json serialization / deserialization. Age int64 Weight float64 }
Ignore a field:
If you want to ignore a field in the structure during json serialization / deserialization, you can add it to the tag as follows -
.
// Use json tag to specify json serialization and deserialization behavior Type Person struct { Name string `json:" name "` // Specify json serialization / deserialization using lowercase name Age int64 Weight float64 `json: "-" `// Ignore this field when specifying json serialization / deserialization }
Ignore empty fields:
When the fields in the struct have no value, json.Marshal()
these fields will not be ignored during serialization, but the default output field's type zero value (for example int
, the float
type zero value is 0, the string
type zero value is ""
, and the object type zero value is nil). If you want to ignore these fields with no value during serialization, you can add omitempty
tags to the corresponding fields .
type User struct { Name string `json:"name"` Email string `json:"email"` Hobby []string `json:"hobby"` } func omitemptyDemo() { u1 := User{ Name: "七米", } // struct -> json string b, err := json.Marshal(u1) if err != nil { fmt.Printf("json.Marshal failed, err:%v\n", err) return } fmt.Printf("str:%s\n", b) }
Output result:
str:{"name":"七米","email":"","hobby":null}
If you want to remove the null value field in the final serialization result, you can define the structure as follows:
// Add omitempty to the tag and ignore the null value // Note that hobby and omitempty together are json tag values, separated by commas in the middle type User struct { Name string `json:" name "` Email string `json:" email, omitempty "` Hobby [] string `json:" hobby, omitempty "` }
At this time, execute the above again omitemptyDemo
, the output is as follows:
str: {"name": "七 米"} // There are no email and hobby fields in the serialized result
Ignore empty value fields of nested structures:
First look at examples of nesting several structures:
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 nestedStructDemo() { u1 := User{ Name: "七米", Hobby: []string{"足球", "双色球"}, } b, err := json.Marshal(u1) if err != nil { fmt.Printf("json.Marshal failed, err:%v\n", err) return } fmt.Printf("str:%s\n", b) }
The Profile
serialized json string is a single layer when anonymous nesting :
str:{"name":"七米","hobby":["足球","双色球"],"site":"","slogan":""}
If you want to turn into a nested json string, you need to change to named nesting or define a field tag:
type User struct { Name string `json:"name"` Email string `json:"email,omitempty"` Hobby []string `json:"hobby,omitempty"` Profile `json:"profile"` } // str:{"name":"七米","hobby":["足球","双色球"],"profile":{"site":"","slogan":""}}
If you want to ignore this field when the nested structure is empty, adding omitempty is not enough:
type User struct { Name string `json:"name"` Email string `json:"email,omitempty"` Hobby []string `json:"hobby,omitempty"` Profile `json:"profile,omitempty"` } // str:{"name":"七米","hobby":["足球","双色球"],"profile":{"site":"","slogan":""}}
You also need to use nested structure pointers:
type User struct { Name string `json:"name"` Email string `json:"email,omitempty"` Hobby []string `json:"hobby,omitempty"` *Profile `json:"profile,omitempty"` } // str:{"name":"七米","hobby":["足球","双色球"]}
Do not modify the original structure and ignore the null value field:
We need json serialization User
, but we do n’t want to serialize the password, and do n’t want to modify the User
structure. At this time, we can use to create another structure PublicUser
anonymous nesting original User
, and specify the Password
field as an anonymous structure pointer type, and add omitempty
tag, The sample code is as follows:
type User struct { Name string `json:"name"` Password string `json:"password"` } type PublicUser struct { *User // 匿名嵌套 Password *struct{} `json:"password,omitempty"` } func omitPasswordDemo() { u1 := User{ Name: "七米", Password: "123456", } b, err := json.Marshal(PublicUser{User: &u1}) if err != nil { fmt.Printf("json.Marshal u1 failed, err:%v\n", err) return } fmt.Printf("str:%s\n", b) // str:{"name":"七米"}
Elegantly handle numbers in string format:
Sometimes, the front end may use string type numbers in the json data passed in. At this time, it can be added in the structure tag string
to tell the json package to parse the data of the corresponding field from the string:
type Card struct { ID int64 `json:"id,string"` // 添加string tag Score float64 `json:"score,string"` // 添加string tag } func intAndStringDemo() { jsonStr1 := `{"id": "1234567","score": "88.50"}` var c1 Card if err := json.Unmarshal([]byte(jsonStr1), &c1); err != nil { fmt.Printf("json.Unmarsha jsonStr1 failed, err:%v\n", err) return } fmt.Printf("c1:%#v\n", c1) // c1:main.Card{ID:1234567, Score:88.5} }
Integer to floating point:
In the JSON protocol, there is no distinction between integer and floating point types, and they are collectively called number. The numbers in the json string will become the float64
type after deserialization of the json package in Go language . The following code demonstrates this problem:
func jsonDemo() { // 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\n", err) } fmt.Printf("str:%#v\n", 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\n", err) return } fmt.Printf("value:%v\n", m2["count"]) // 1 fmt.Printf("type:%T\n", m2["count"]) // float64 }
In this scenario, if you want to handle numbers more reasonably, you need to use decoder
deserialization. The sample code is as follows
func decoderDemo() { // 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\n", err) } fmt.Printf("str:%#v\n", 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\n", err) return } fmt.Printf("value:%v\n", m2["count"]) // 1 fmt.Printf ("type:% T \ n", m2 ["count"]) // json.Number // After converting m2 ["count"] to json.Number, call the Int64 () method to obtain a value of type int64 count, err: = m2 ["count"]. (json.Number) .Int64 () if err! = nil { fmt.Printf ("parse to int64 failed, err:% v \ n", err) return } fmt .Printf ("type:% T \ n", int (count)) // int
The source code of json.Number is defined as follows:
// 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) }
We need to get the json.Number type when processing the number type json field, and then call Float64 () or Int64 () according to the actual type of the field.
Custom resolution time field:
The built-in json package of Go language uses the time format defined in the RFC3339 standard, which has many restrictions when we serialize the time field.
type Post struct { CreateTime time.Time `json:"create_time"` } func timeFieldDemo() { p1 := Post{CreateTime: time.Now()} b, err := json.Marshal(p1) if err != nil { fmt.Printf("json.Marshal p1 failed, err:%v\n", err) return } fmt.Printf("str:%s\n", b) jsonStr := `{"create_time":"2020-04-05 12:25:42"}` var p2 Post if err := json.Unmarshal([]byte(jsonStr), &p2); err != nil { fmt.Printf("json.Unmarshal failed, err:%v\n", err) return } fmt.Printf("p2:%#v\n", p2) }
The output of the above code is as follows:
str:{"create_time":"2020-04-05T12:28:06.799214+08:00"} json.Unmarshal failed, err:parsing time ""2020-04-05 12:25:42"" as ""2006-01-02T15:04:05Z07:00"": cannot parse " 12:25:42"" as "T"
That is, the built-in json package does not recognize our commonly used string time format, such as 2020-04-05 12:25:42.
However, we implement custom event format parsing by implementing the json.Marshaler / json.Unmarshaler interface.
type CustomTime struct { time.Time } const ctLayout = "2006-01-02 15:04:05" var nilTime = (time.Time{}).UnixNano() func (ct *CustomTime) UnmarshalJSON(b []byte) (err error) { s := strings.Trim(string(b), "\"") if s == "null" { ct.Time = time.Time{} return } ct.Time, err = time.Parse(ctLayout, s) return } func (ct *CustomTime) MarshalJSON() ([]byte, error) { if ct.Time.UnixNano() == nilTime { return []byte("null"), nil } return []byte(fmt.Sprintf("\"%s\"", ct.Time.Format(ctLayout))), nil } func (ct *CustomTime) IsSet() bool { return ct.UnixNano() != nilTime } type Post struct { CreateTime CustomTime `json:"create_time"` } func timeFieldDemo() { p1 := Post{CreateTime: CustomTime{time.Now()}} b, err := json.Marshal(p1) if err != nil { fmt.Printf("json.Marshal p1 failed, err:%v\n", err) return } fmt.Printf("str:%s\n", b) jsonStr := `{"create_time":"2020-04-05 12:25:42"}` var p2 Post if err := json.Unmarshal([]byte(jsonStr), &p2); err != nil { fmt.Printf("json.Unmarshal failed, err:%v\n", err) return } fmt.Printf("p2:%#v\n", p2) }
Custom MarshalJSON and UnmarshalJSON methods:
The custom type method above is a bit more verbose. Let's look at a relatively convenient method.
The first thing you need to know is that if you can implement MarshalJSON()([]byte, error)
and UnmarshalJSON(b []byte) error
methods for a type , then this type will use the corresponding method you customized when serializing (MarshalJSON) / deserializing (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 implements a custom MarshalJSON method for Order type func (o * Order) MarshalJSON () ([] byte, error) { type TempOrder Order // define a new type consistent with the Order field return json.Marshal (struct { CreatedTime string `json:" created_time "` * TempOrder // Avoid nesting Order directly into an endless loop } { CreatedTime: o.CreatedTime.Format (layout), TempOrder: (* TempOrder) (o), }) } // UnmarshalJSON implements a custom UnmarshalJSON method for Order type func func (o * Order) UnmarshalJSON (data [] byte) error { type TempOrder Order // defines a new type consistent with the Order field ot: = struct { CreatedTime string `json:" created_time "` * TempOrder // Avoid directly nesting Order into an infinite loop } { 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 } // Custom serialization method func customMethodDemo () { o1: = Order { ID: 123456, Title: "" Go Study Notes of Seven Meters "", CreatedTime: time.Now (), } // implement struct-> json string b, err through custom MarshalJSON method : = json.Marshal (& o1) if err! = nil { fmt.Printf ("json.Marshal o1 failed, err:% v \ n", err) return } fmt.Printf ("str:% s \ n", b) // Through custom The UnmarshalJSON method implements json string- > struct jsonStr: = `{" created_time ":" 2020-04-05 10:18:20 "," id ": 123456," title ":" "Go Learning Notes for Seven Meters" " } ` var o2 Order if err: = json.Unmarshal ([] byte (jsonStr), & o2); err! = nil { fmt.Printf (" json.Unmarshal failed, err:% v \ n ", err) return } fmt.Printf ("o2:% # v \ n", o2) }
Output result:
str: {"created_time": "2020-04-05 10:32:20", "id": 123456, "title": "" Go Learning Notes of Seven Meters ""} o2: main.Order {ID: 123456 , Title: "" Go Learning Notes for Seven Meters "", CreatedTime: time.Time {wall: 0x0, ext: 63721678700, loc: (* time.Location) (nil)}}
Add field using anonymous structure
Using the embedded structure can expand the fields of the structure, but sometimes we don't need to define a new structure separately, you can use anonymous structures to simplify operations:
type UserInfo struct { ID int `json:"id"` Name string `json:"name"` } func anonymousStructDemo() { u1 := UserInfo{ ID: 123456, Name: "七米", } // 使用匿名结构体内嵌User并添加额外字段Token b, err := json.Marshal(struct { *UserInfo Token string `json:"token"` }{ &u1, "91je3a4s72d1da96h", }) if err != nil { fmt.Printf("json.Marsha failed, err:%v\n", err) return } fmt.Printf("str:%s\n", b) // str:{"id":123456,"name":"七米","token":"91je3a4s72d1da96h"} }
Use anonymous structures to combine multiple structures
Similarly, you can also use anonymous structures to combine multiple structures to serialize and deserialize data:
type Comment struct { Content string } type Image struct { Title string `json:"title"` URL string `json:"url"` } func anonymousStructDemo2() { c1 := Comment{ Content: "永远不要高估自己", } i1 := Image{ Title: "赞赏码", URL: "https://www.liwenzhou.com/images/zanshang_qr.jpg", } // struct -> json string b, err := json.Marshal(struct { *Comment *Image }{&c1, &i1}) if err != nil { fmt.Printf("json.Marshal failed, err:%v\n", err) return } fmt.Printf("str:%s\n", b) // json string -> struct jsonStr := `{"Content":"永远不要高估自己","title":"赞赏码","url":"https://www.liwenzhou.com/images/zanshang_qr.jpg"}` var ( c2 Comment i2 Image ) if err := json.Unmarshal([]byte(jsonStr), &struct { *Comment *Image }{&c2, &i2}); err != nil { fmt.Printf("json.Unmarshal failed, err:%v\n", err) return } fmt.Printf("c2:%#v i2:%#v\n", c2, i2) }
Output:
str: {"Content": "Never overestimate yourself", "title": "Appreciation Code", "url": "https://www.liwenzhou.com/images/zanshang_qr.jpg"} c2: main. Comment {Content: "Never overestimate yourself"} i2: main.Image {Title: "Appreciation Code", URL: "https://www.liwenzhou.com/images/zanshang_qr.jpg"}
Handling json at an uncertain level
If the json string has no fixed format and it is difficult to define the corresponding structure, we can use the json.RawMessage
original byte data to save it.
type sendMsg struct { User string `json:" user "` Msg string `json:" msg "` } func rawMessageDemo () { jsonStr: = `{" sendMsg ": {" user ":" q1mi "," msg ": "Never overestimate yourself"}, "say": "Hello"} ` // Define a map with a value type of json.RawMessage to facilitate more flexible subsequent processing of var data map [string] json.RawMessage if err: = json.Unmarshal ([] byte (jsonStr), & data); err! = nil { fmt.Printf ("json.Unmarshal jsonStr failed, err:% v \ n", err) return } var msg sendMsg if err: = json .Unmarshal (data ["sendMsg"], & msg); err! = Nil { fmt.Printf ("json.Unmarshal failed, err:% v \ n",err) return } fmt.Printf("msg:%#v\n", msg) // msg: main.sendMsg {User: "q1mi", Msg: "Never overestimate yourself"} }