Go 中的结构体和 JSON 序列化

Go 中的结构体和 JSON 序列化

前面我们或多或少的都使用了结构体这种数据结构,本身结构体也有很多特性,我们一一来看。

结构体的作用是将一个或者多个任一类型的变量组合在一起的数据类型,类似于我们在 Java 中 class 的作用。在结构体重也可以嵌套结构体。结构体还可以有自己的方法。

1.定义结构体

我们先定义一个结构体:

结构体定义如下:

type 标识符 struct {
  field1 type
 field2 type

例子:

type Staff struct {
 UserId int16
 UserName string
 Sex byte
 Age int8

2. 使用结构体

有三种方式可以使用结构体:

var staff Staff
staff1 := new(Staff)
staff2 := &Staff{}

上面 2 和 3 的效果是一样的,返回的都是指向结构体的指针。

Go 中的结构体不像 class 一样有构造函数,一般会使用上述第三种方式来构造出一个结构体对象,简称 Go 中的工厂模式:

package main
import "fmt"
type Staff struct {
 UserId int16
 UserName string
 Sex byte
 Age int8
func NewStaff(userId int16, userName string, sex byte, age int8) *Staff {
 return &Staff{
  UserId:userId,
  UserName:userName,
  Sex:sex,
  Age:age,
func main() {
 staff := NewStaff(123,"xiaoming",byte(1),13)
 fmt.Println(staff)

3. 带标签的结构体

结构体中的字段除了有名字和类型外,还可以有一个可选的标签(tag):它是一个附属于字段的字符串,可以是文档或其他的重要标记。 标签的内容不可以在一般的编程中使用, 只有通过反射机制才能能获取它

我们现在有这样一段程序:从 json 文件中读取 json 字符串,然后转为 json 对象:

json 文件内容:

{
    "port": "7788",
    "address": "47.95.34.2"

代码如下:

package main
import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
type MainConfig struct {
    port                string
    address             string
func LoadConfig(path string) *MainConfig {
    buf, err := ioutil.ReadFile(path)
    if err != nil {
        log.Panicln("load config conf failed: ", err)
    mainConfig := &MainConfig{}
    err = json.Unmarshal(buf, mainConfig)
    if err != nil {
        log.Panicln("decode config file failed:", string(buf), err)
    fmt.Println(mainConfig.address,mainConfig.port)
    return mainConfig
func main() {
    LoadConfig("c:/test.json")

执行以上程序可以发现打印出来的结果是空的,原因是: Go 开发规范认为:只有开头是大写字母的对象,方法才被认为是公开的,可以在包外访问,否则就是私有的,外部对象无法访问。

那我们定义结构体大写之后,但是想让结构体中的字段 json 格式化为小写应该怎么做呢?这时候就可以通过 tag 来指定:

package main
import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
type MainConfig struct {
    Port string `json:"port"`
    Address string `json:"address"`
//反射获取字段中的tag
func reflectTag(mg MainConfig)  {
    mgType := reflect.TypeOf(mg)
    tag0 := mgType.Field(0).Tag
    tag1 := mgType.Field(0).Tag
    fmt.Println(tag0,tag1)
func LoadConfig(path string) *MainConfig {
    buf, err := ioutil.ReadFile(path)
    if err != nil {
        log.Panicln("load config conf failed: ", err)
    mainConfig := &MainConfig{}
    err = json.Unmarshal(buf, mainConfig)
    if err != nil {
        log.Panicln("decode config file failed:", string(buf), err)
    bytes, err := json.Marshal(mainConfig)
    fmt.Println(string(bytes))
    return mainConfig
func main() {
    LoadConfig("c:/test.json")
    config := MainConfig{"1234", "123.221.134"}
    reflectTag(config)
打印结果
{"port":"7788","address":"47.95.34.2"}
json:"port" json:"port"

上面的代码是一段从 json 文件中读取配置文件的例子,也使用了反射机制来获取 tag 标签,使用 json.Unmarshal 来反序列化,使用 json.Marshal 将对象序列化为 json 字符串。

4.方法

其实 Go 中任何自定义的类型都可以有方法,不仅仅是 struct 才有。除了指针和 interface。

看一个自定义类型带方法的例子:

package main
import "fmt"
type MainConfig1 struct {
    Port string `json:"port"`
    Address string `json:"address"`
type Str string
func (s Str) Compact(str string,str1 string) string  {
    return str + str1
func (s *Str) Compact1(str string,str1 string) Str {
    return Str(str + str1)
//mf相当于其他语言中的this,self表示当前对象本身
func (mf MainConfig1) Compact2() Str {
    return Str(mf.Port + "|" + mf.Address)
func main() {
    var s Str
    compact1 := s.Compact("a", "b")
    str := s.Compact1("c", "d")
    fmt.Println(compact1,str)
    var mf MainConfig1
    mf.Port = "2"
    mf.Address = "3333"
    mf.Compact2()

声明 Str 类型的 s,通过 s 可以调用这两个方法。另外还记得 Go 的访问控制规范吧,首字母大写表示公共方法,小写表示私有,自定义类型的方法同样遵循这个原则,试着把 Compact1 方法的首字母改为小写,你会发现通过 s.Compact1("c", "d") 找不到方法。

再看 Compact2() 方法的使用,使用当前接收者本身来获取属性,跟 Java 中的 this 关键字相似。

上面还有一个疑问点:

使用 Str 和使用 *Str 作为接受者有什么不同呢?

区别就在于: 在接收者是指针时,方法可以改变接收者的值(或状态)

我们来改造一下 Compact2() 方法:

func (mf MainConfig1) Compact2() Str {
    mf.Address = "rrrr"
    return Str(mf.Port + "|" + mf.Address)
var mf MainConfig1
mf.Port = "2"
mf.Address = "3333"
compact2 := mf.Compact2()
fmt.Println(compact2)
bytes, _ := json.Marshal(mf)
fmt.Println(string(bytes))

输出的结果是:

2|rrrr
{"port":"2","address":"3333"}

函数作用域内的 Address 值被改变了,但是实际上 mf 对象的属性值并没有被改变。

那么我们将 receiver 改为指针试一下:

func (mf *MainConfig1) Compact2() Str {
    mf.Address = "rrrr"
    return Str(mf.Port + "|" + mf.Address)
var mf MainConfig1
mf.Port = "2"
mf.Address = "3333"
compact2 := mf.Compact2()