前言
结构体是将多个任意类型的命名变量组合在一起的聚合数据类型,通过结构体,可以多维度/方面的聚合逻辑数据,形成一个整体,其中的这些命名变量叫做结构体的成员。
Struct
声明:
// 声明一个结构体类型
type Employee struct {
ID int
Name string
Address string
DoB string
Position string
Salary int
ManagerID int
}
结构体成员(变量名称)推荐使用大写字母开头,目的是为了方便与json进行转换,具体说明可以看后面的部分。
简单使用
// 声明结构体类型的实例,三种方式,前两种声明的都是指针,后面一种是直接生产对象本身,初始属性值都是零值:
var bob *Employee = new(Employee) // &{0 0 0}
var claier *Employee = &Employee{} // &{0 0 0}
var alice Employee // {0 0 0}
fmt.Println(bob, claier, alice)
// 访问和赋值
alice.Salary = 3000
fmt.Println(alice, alice.Salary) // {0 3000 0} 3000
// 通过指针访问和赋值
position := &bob.Position
*position = "Senior"
fmt.Println(bob) // &{0 Senior 0 0}
c := &claier
(*c).Name = "claier"
fmt.Println(*c) // &{0 alice Senior 3000 0}
构造和比较
/*
构造函数:
struct结构体默认没有构造函数,需要自己写一个
*/
func NewEmployee(id int, name string) *Employee {
return &Employee{ID: id, Name: name}
}
david := NewEmployee(4, "david")
fmt.Println(*david) // {4 david 0 0}
/*
struct之间的比较
*/
// struct是可以直接进行比较的,编译不会报错。此前学习过,map的key必须是可以直接比较的类型,因此struct可以作为map的key
fmt.Println(alice == *bob) // false
type url struct {
host string
port int
}
pageViews := make(map[url]int)
pageViews[url{"www.baidu.com", 443}]++
结构体嵌套
/*
struct嵌套
struct支持多层嵌套,且可以将一个命名结构体当做另一个结构体的匿名成员使用,下面举一个例子:
层级关系为: 学生 -> 班级 -> 年级 -> 学校
*/
// 基本用法
type School struct {
name string
}
type Grade struct {
level int
school School
}
type Classes struct {
grade Grade
id int
}
type Student struct {
name string
age int
classes Classes
}
var xiaoming Student
xiaoming.name = "xiaoming"
xiaoming.classes.grade.school.name = "NO.1 School"
xiaoming.classes.grade.level = 3
xiaoming.classes.id = 2
xiaoming.age = 8
fmt.Println(xiaoming) // {xiaoming 8 {{3 {NO.1 School}} 2}}
匿名结构体嵌套
/*
匿名结构体嵌套:
匿名用法,结构体内的不带名称的结构体成员,该成员必须是其他类型的对象或指针。使用匿名成员时,可以以穿透的方式给属性赋值,即当前结构体不包含该 \n
属性名时,则往向上层级的结构体上查找该属性名,最近的匹配属性将获得赋值(最近原则)。例如下方,xiaoli.name的值赋给了Student2.name, 而 \n
xiaoli.classes.grade.school.name,则可以通过xiaoli.School.name来赋值。
虽然是比直接用法简化了一些,但感觉层次还是不够清晰,谨慎使用。
*/
type Grade2 struct {
level int
School
}
type Classes2 struct {
Grade2
id int
}
type Student2 struct {
name string
age int
Classes2
}
var xiaoli Student2
xiaoli.name = "xiaoli"
xiaoli.School.name = "NO.2 School" // 等价于上面的 xiaoli.classes.grade.school.name
xiaoli.level = 4 // 等价于上面的 xiaoli.classes.grade.level
xiaoli.id = 3 // 等价于上面的 xiaoli.classes.id
xiaoli.age = 9
fmt.Println(xiaoli) // {xiaoli 9 {{4 {NO.2 School}} 3}}
// 匿名结构体赋值
xiaohong := Student2{"xiaohong", 11, Classes2{Grade2{5, School{"NO.2 School"}}, 4}}
// 等价于上方
xiaotian := Student2{
"xiaotian",
12,
Classes2{
Grade2{
6,
School{
"NO.2 School",
},
},
5,
},
}
fmt.Println(xiaohong) // {xiaohong 11 {{5 {NO.2 School}} 4}}
fmt.Println(xiaotian) // {xiaotian 12 {{6 {NO.2 School}} 5}}
JSON
Json是非常常见的数据接收和发送的格式化标准,其逻辑结构与struct非常相似,go提供标准库encoding/json来支持对json数据的处理和转换操作。
下面举例说明struct和json之间的转换:
struct => json序列化
type Movie struct {
Name string
Year int
actor []string
}
var movies = []Movie{
{Name: "movie1", Year: 1999, actor: []string{"zhangsan", "lisi"}},
{Name: "movie2", Year: 2000, actor: []string{"wangwu", "zhaoliu"}},
}
data, err := json.Marshal(movies)
if err != nil {
fmt.Println("Json parse error")
return
}
fmt.Println(movies) // [{movie1 1999 [zhangsan lisi]} {movie2 2000 [wangwu zhaoliu]}]
fmt.Printf("%s\n", data) // [{"Name":"movie1","Year":1999},{"Name":"movie2","Year":2000}]
//这里的打印结果可以看出一个问题: actor属性并没有体现出来,这是因为JSON在处理struct转换的时候,只会处理属性名为大写的属性,小写的直接跳过不处理。
上面这个例子中,json序列化时会忽略小写开头的结构体成员,如果想要在json处理时显示成员为小写或别名,可以显示地为其添加标签:
type Movie2 struct {
Name string
Year int
Actor []string `json:"actor"`
}
var movies2 = []Movie2{
{Name: "movie1", Year: 1999, Actor: []string{"zhangsan", "lisi"}},
{Name: "movie2", Year: 2000, Actor: []string{"wangwu", "zhaoliu"}},
}
data2, _ := json.Marshal(movies2)
fmt.Printf("%s\n", data2) // [{"Name":"movie1","Year":1999,"actor":["zhangsan","lisi"]},{"Name":"movie2","Year":2000,"actor":["wangwu","zhaoliu"]}]
json:"actor"
json => struct反序列化
// 沿用上面例子的变量和结构体
//var movies3 Movie2
// 这里犯了一个错误,导致运行报错.记一笔:
// 将变量定义为Movie2结构体类型之后去接收这里的data2的json反序列化数据时,报错:json: cannot unmarshal array into Go value of type main.Movie2
// 原因为: 当json串处理之后的结果是数组类型的时候,不能直接赋值给struct类型的值,因此,这里定义的movies3变量应该是[]Movie2的slice类型。go语言之中类型必须严格匹配,注意!
// 另外,除了这里的unmarshal方式之外,还有一种json.Decoder的方式可以依次从字节流里逐个解码多个json对象,这个在后面的例子json_template.go中做解释。
var movies3 []Movie2
err2 := json.Unmarshal(data2, &movies3)
if err2 != nil {
fmt.Println("Json parse error", err2)
}
fmt.Printf("%v\n", movies3) // [{movie1 1999 [zhangsan lisi]} {movie2 2000 [wangwu zhaoliu]}]
fmt.Println(movies3[0].Year) // 1999
[]Movie2[]Movie2
这里使用的Unmarshal方法适用的对象为已经在单个或已内存中的json对象,而涉及文件(socket/http等)读取的byte流json数据,则适合使用另一种反序列化方式json.Decoder(),这种方式不会一次把字节流读取进内存中,而是会逐个读取,适用于这些场景。
使用举例
package main
/*
通过Github issue api获取到json数据并解析为go struct的demo
*/
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"os"
"strings"
"time"
)
const IssueURL = "https://api.github.com/search/issues"
type IssuesSearchResult struct {
TotalCount int `json:"total_count"`
Items []*Issue
}
type Issue struct {
Number int
HTMLURL string `json:"html_url"`
Title string
State string
User *User
CreateAt time.Time `json:"created_at"`
Body string
}
type User struct {
Login string
HTMLURL string `json:"html_url"`
}
func SearchIssues(terms []string) (*IssuesSearchResult, error) {
q := url.QueryEscape(strings.Join(terms, " ")) // slice组合为字符串
resp, err := http.Get(IssueURL + "?q=" + q)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, err
}
var result IssuesSearchResult
// 使用json.NewDecoder().decode()方法来解码整个字节流,这里不使用unmarshal方法了,unmarshal方法只能解码单个对象。
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
resp.Body.Close()
return nil, err
}
resp.Body.Close()
return &result, nil
}
func main() {
res, err := SearchIssues(os.Args[1:]) // 运行命令为: go run ./json_template.go repo:golang/go is:open json decoder 后方为参数,可以组合为url去浏览器打开对比
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Items[0].Title)
fmt.Printf("%d issues: \n", res.TotalCount)
for _, item := range res.Items {
fmt.Printf(
"IssueID: #%d\tTime: %s\tUser: %s\tTitle: %s\n", item.Number, item.CreateAt, item.User.Login, item.Title)
}
}
运行结果:
encoding/json: second decode after error impossible
35 issues:
IssueID: #31701 Time: 2019-04-26 20:50:17 +0000 UTC User: lr1980 Title: encoding/json: second decode after error impossible
IssueID: #31789 Time: 2019-05-01 18:30:06 +0000 UTC User: mgritter Title: encoding/json: provide a way to limit recursion depth
IssueID: #29688 Time: 2019-01-11 17:07:05 +0000 UTC User: sheerun Title: proposal: encoding/json: add InputOffset to json decoder
IssueID: #29686 Time: 2019-01-11 16:38:04 +0000 UTC User: sheerun Title: json: Add InputOffset for stream byte offset access
IssueID: #28923 Time: 2018-11-22 13:50:18 +0000 UTC User: mvdan Title: encoding/json: speed up the decoding scanner
IssueID: #11046 Time: 2015-06-03 19:25:08 +0000 UTC User: kurin Title: encoding/json: Decoder internally buffers full input
IssueID: #29035 Time: 2018-11-30 11:21:31 +0000 UTC User: jaswdr Title: proposal: encoding/json: add error var to compare the returned error when using json.Decoder.DisallowUnknownFields()
IssueID: #30301 Time: 2019-02-18 19:49:27 +0000 UTC User: zelch Title: encoding/xml: option to treat unknown fields as an error
IssueID: #30701 Time: 2019-03-09 12:16:34 +0000 UTC User: LouAdrien Title: encoding/json: ignore tag "-" not working on embedded sub structure when decoding
IssueID: #12001 Time: 2015-08-03 19:14:17 +0000 UTC User: lukescott Title: encoding/json: Marshaler/Unmarshaler not stream friendly
IssueID: #16212 Time: 2016-06-29 16:07:36 +0000 UTC User: josharian Title: encoding/json: do all reflect work before decoding
IssueID: #28143 Time: 2018-10-11 07:08:25 +0000 UTC User: Carpetsmoker Title: proposal: encoding/json: add "readonly" tag
IssueID: #26946 Time: 2018-08-12 18:19:01 +0000 UTC User: deuill Title: encoding/json: clarify what happens when unmarshaling into a non-empty interface{}
IssueID: #5901 Time: 2013-07-17 16:39:15 +0000 UTC User: rsc Title: encoding/json: allow override type marshaling
IssueID: #27179 Time: 2018-08-23 18:21:32 +0000 UTC User: lavalamp Title: encoding/json: no way to preserve the order of map keys
IssueID: #21823 Time: 2017-09-09 21:43:31 +0000 UTC User: 243083df Title: encoding/xml: very low performance in xml parser
IssueID: #22752 Time: 2017-11-15 23:46:13 +0000 UTC User: buyology Title: proposal: encoding/json: add access to the underlying data causing UnmarshalTypeError
IssueID: #14750 Time: 2016-03-10 13:04:44 +0000 UTC User: cyberphone Title: encoding/json: parser ignores the case of member names
IssueID: #20754 Time: 2017-06-22 14:19:31 +0000 UTC User: rsc Title: encoding/xml: unmarshal only processes first XML element
IssueID: #28189 Time: 2018-10-13 16:22:53 +0000 UTC User: adnsv Title: encoding/json: confusing errors when unmarshaling custom types
IssueID: #7213 Time: 2014-01-27 00:23:01 +0000 UTC User: davecheney Title: cmd/compile: escape analysis oddity
IssueID: #7872 Time: 2014-04-26 17:47:25 +0000 UTC User: extemporalgenome Title: encoding/json: Encoder internally buffers full output
IssueID: #20528 Time: 2017-05-30 11:45:14 +0000 UTC User: jvshahid Title: net/http: connection reuse does not work happily with normal use of json package
IssueID: #17609 Time: 2016-10-26 16:07:27 +0000 UTC User: nathanjsweet Title: encoding/json: ambiguous fields are marshalled
IssueID: #19636 Time: 2017-03-21 12:25:10 +0000 UTC User: josselin-c Title: encoding/base64: decoding is slow
IssueID: #22816 Time: 2017-11-20 13:12:06 +0000 UTC User: ganelon13 Title: encoding/json: include field name in unmarshal error messages when extracting time.Time
IssueID: #21092 Time: 2017-07-19 23:11:40 +0000 UTC User: trotha01 Title: encoding/json: unmarshal into slice reuses element data between len and cap
IssueID: #28941 Time: 2018-11-25 14:06:38 +0000 UTC User: mvdan Title: cmd/compile: teach prove about slice expressions
IssueID: #15808 Time: 2016-05-24 02:13:10 +0000 UTC User: randall77 Title: cmd/compile: loads/constants not lifted out of loop
IssueID: #28952 Time: 2018-11-26 09:59:26 +0000 UTC User: mvdan Title: cmd/compile: consider teaching prove about unexported integer fields
总结
Golang结构体有许多特性,且与json结合紧密,日后会经常用到,需要熟练掌握。