Go与Json

骑驴找蚂蚁 · 2018-09-21

Go像其它语言一样提供了很多标准库, JSON也是其中之一。官网也提供了完整的文档,但是对于初学者来说不是很好理解。所以今天们将学习下Go语言JSON库的用法.

JSON与静态类型

对于像Go这种静态类型语言来说JSON这种数据格式会有一些问题。JSON的内容可以是任何内容,编译器是如何知道内存放置的任何内容呢?

这里有两个答案,当你名确知道数据的字段、类型时,可以将JSON解析为你定义的结构体. 任何不适合结构体中的字段都被忽略。下面代码将JSON字符串转为结构体.

解析为结构体


package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Id int32 `json:"id"`
    Name string `json:"name"`
}

func main() {
    jsonString := []byte(`
        {
            "id": 1, "name": "meShell"
        }   
    `)
    var meShell User
    json.Unmarshal(jsonString, &meShell) //将内容解析到meShell变量上

    fmt.Printf("%+v", meShell)
}

你会发现GoUnmarshal作为转换函数运行之后的结果.


meShell: {Id:1 Name:meShell}
Process finished with exit code 0

结构体到JSON字符串

将结构体转为[JSON][]数据类型字符串与上面一样只是函数名改变.


stringJson, _ := json.Marshal(meShell)

与Go中的所有结构一样,重要的是要记住,只有具有大写第一个字母的字段对于JSON Marshaller等外部程序是可见的。


type jsonStruct struct {
    hidden int32
    Show int32
}
jsonStructString, _ := json.Marshal(jsonStruct{hidden: 1, Show: 2})
fmt.Printf("%s", jsonStructString)

大家可以运行下上面的代码的打印结果就知道了上面说得外部程序可见是什么意思.

结构标签

你会注意到我们的结构中反引号之间包含的”标记”数据。 JSON解析器从那几条关于如何解析该值的线索中读取。

在上面的示例中你已知道,Go要求所有导出的字段都以大写字母开头。然而,在JSON中使用该样式并不常见。所以我们使用标记让解析器知道实际查找值的位置。

就像下面的代码一样:


type MyStruct struct {
    SomeField string `json:"some_field"`
}

值为空怎么办

JSON解析器还接受标记中的标记,以便在字段为空时让它知道该怎么做。 omitempty标志告诉它在输出中不包含JSON值,如果它是该类型的”空值”。


type MyStruct struct {
    //SomeField string `json:"some_field"`  //大家可以打印下加和不加的结果区别
    SomeField string `json:"some_field,omitempty"`
}
stringJson, _ := json.Marshal(MyStruct{})
fmt.Printf("%s", stringJson)

如果 if some_field == “”:

  • 加上omitempty 结果将是{}.
  • 不加omitempty 结果将是{“some_field”: “”}.

跳过字段

要让JSON解析器跳过一个字段,只需给它命名json:"-"即可.


type User struct {
    Id int32
    Name string
    Password string `json:"-"`
}
stringJson, _ := json.Marshal(User{})

fmt.Printf("%s", stringJson)

运行结果:{"Id":0,"Name":""}.

嵌套字段

嵌套字段是指作为其他结构的属性的结构。您可以在结构中嵌套切片或其他结构,JSON将以递归方式正确解析所有内容。如果您有一个可以是任何内容的字段,则可以始终使用interface {}类型。


    type User struct {
        Id int32 `json:"id"`
    }

    type Role struct {
        Id int32 `json:"-"`
        Name string `json:"name"`
        Time string `json:"time"`
    }

    type UserWithRole struct {
        User 
        Role 
    }
    var userWithRole UserWithRole
    data := []byte(`{"Id": 1, "Name": "管理员"}`)
    json.Unmarshal(data, &userWithRole)

    fmt.Printf("%+v", userWithRole)

运行结果:{User:{Id:1} Role:{Id:0 Name:管理员 Time:}}.

处理错误

务必检查Marshal和Unmarshal返回的err参数。例如,您将如何知道您正在解析的JSON的语法是否存在错误。如果不进行检查,程序将继续执行已经创建的清零结构,这可能会导致下面的程序逻辑混乱。

如果你不想处理每个转换的处理错误,并且转换错误是不常见的,你可以自己定义一个通用函数将错误转换为异常:


func CommonMarshal(data interface{}) []byte {
    out, err := json.Marshal(data)
    if err != nil {
        panic(err)
    }
    return out
}

func CommonUnmarshal([]byte jsonString, data interface{}) {
    err := json.Unmarshal(jsonString, &data)
    if err != nil {
        panic(err)
    }
}

解码任意数据

如果您真的不知道您的JSON可能是什么样的,您可以将其解析为通用interface {}。如果您不熟悉空interface {}是一种在Go中定义变量的方法,“这可能是任何东西”。
在运行时,Go将分配适当的内存以适合您决定存储在其中的任何内容。


var parsed interface{}
json.Unmarshal([]byte(`{"id": 1, "name": "meShell"}`), &parsed)

fmt.Printf("%+v", parsed)

json包使用六种类型解析内容:

  • map[string]interface{} 任意数据对象
  • []interface{} 任意数组对象
  • bool
  • float64
  • string
  • nil

像上面的示例代码我们可以通过parsed.(map[string]interface{})断言他是这种类型.


jsonObject := parsed.(map[string]interface{})

for k, v := range jsonObject {
    switch vv := v.(type) {
    case string:
        fmt.Println(k, "is string", vv)
    case float64:
        fmt.Println(k, "is float64", vv)
    case []interface{}:
        fmt.Println(k, "is an array:")
        for i, u := range vv {
            fmt.Println(i, u)
        }
    default:
        fmt.Println(k, "is of a type I don't know how to handle")
    }
}

运行结果:


id is float64 1
name is string meShell

自定义对象JSON

你可以通过文档发现JSON模块包括两个接口[Marshaler][]和[Unmarshaler][]下面将通过示例来展示.


package main

import (
    "fmt"
    "strconv"
    "strings"
)

type User struct {
    Id int32
    Name string
}

func (user User) MarshalJSON() ([]byte, error) {
    return []byte(fmt.Sprintf("%d|%s", user.Id, user.Name)), nil
}

func (user *User) UnmarshalJSON(value []byte) (error) {
    split := strings.Split(string(value), "|")

    id, _ := strconv.ParseInt(split[0], 10, 32)

    user.Id = int32(id)

    user.Name = split[1]

    return nil
}

func main() {

    user := User{Id: 9527, Name:"meShell"}

    jsonString, _ := user.MarshalJSON()
    fmt.Printf("%s\n", jsonString)

    userTwo := new(User)

    userTwo.UnmarshalJSON([]byte("100|骑驴找蚂蚁"))

    fmt.Printf("%+v\n", userTwo)
}

运行结果:


9527|meShell
&{Id:100 Name:骑驴找蚂蚁}

延迟处理

使用RawMessage来延迟解析部分JSON消息


package main

import (
    "encoding/json"
    "fmt"
    "log"
)

func main() {
    type Color struct {
        Space string
        Point json.RawMessage // delay parsing until we know the color space
    }
    type RGB struct {
        R uint8
        G uint8
        B uint8
    }
    type YCbCr struct {
        Y  uint8
        Cb int8
        Cr int8
    }

    var j = []byte(`[
    {"Space": "YCbCr", "Point": {"Y": 255, "Cb": 0, "Cr": -10}},
    {"Space": "RGB",   "Point": {"R": 98, "G": 218, "B": 255}}
]`)
    var colors []Color
    err := json.Unmarshal(j, &colors)
    if err != nil {
        log.Fatalln("error:", err)
    }

    for _, c := range colors {
        var dst interface{}
        switch c.Space {
        case "RGB":
            dst = new(RGB)
        case "YCbCr":
            dst = new(YCbCr)
        }
        err := json.Unmarshal(c.Point, dst)
        if err != nil {
            log.Fatalln("error:", err)
        }
        fmt.Println(c.Space, dst)
    }
}

Go的JSON讲解决我们就到此为止了,各位可以跑下文章中的代码示例.

推荐阅读

  1. https://blog.golang.org/json-and-go
  2. https://golang.org/pkg/encoding/json