快轉到主要內容
  1. 技術/

Golang JSON 處理

·3 分鐘

JSON(JavaScript Object Notation)是一種輕量級的資料交換語言,以純文字為基礎去儲存資料,有相容性高、易於理解且許多程式語言都支援等優點。

而在 Golang 之中是如何處理 JSON 的呢?

Go JSON

Golang 官方的 package 中已有支援 JSON,可很容易的對 JSON 數據做編解碼

數據結構
#

typesJSONGolang
字串stringstring
整數numberint
浮點數numberflaot64
陣列arrayslice
物件objectstruct
布林boolbool
空值nullnil

JSON 編碼
#

JSON 套件裡面透過 Marshal 函數來將數據處理成 JSON 字串

func Marshal(v interface{}) ([]byte, error)
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID int
    Name string
    Money float64
    Skills []string
    Relationship map[string]string
    Identification Identification
}

type Identification struct {
    Phone bool
    Email bool
}

func main() {
    user := User {
        ID:     1,
        Name:   "Tony",
        Skills: []string{"program", "rich", "play"},
        Relationship: map[string]string {
            "Dad": "Hulk",
            "Mon": "Natasha",
        },
        Identification: Identification {
            Phone: true,
            Email: false,
        },
    }
    b, err := json.Marshal(user)
    if err != nil {
        fmt.Println("error:", err)
    }
    fmt.Println(string(b))
}

輸出:

{   
    "ID":1,
    "Name":"Tony",
    "Money":0,
    "Skills":
    [
        "program",
        "rich","play"
    ],
    "Relationship":
    {
        "Dad":"Hulk",
        "Mon":"Natasha"
    },
    "Identification":
    {
        "Phone":true,
        "Email":false
    }
}

從上面的例子看到 key 值都是大寫,因只有大寫的才會被輸出,但若是需要小寫或其他的名稱怎麼辦呢? Golang 提供了 struct tag 來實現

...
type Identification struct {
    Phone bool `json:"phone"`
    Email bool `json:"email"`
}
...
{
    "ID":1,
    "Name":"Tony",
    "Money":0,
    "Skills":
    [
        "program","rich","play"
    ],
    "Relationship":
    {
        "Dad":"Hulk",
        "Mon":"Natasha"
    },
    "Identification":
    {
        "phone":true,
        "email":false
    }
}

struct tag 還可以有以下功能(取自官方文件)
#

// Field appears in JSON as key "myName".
Field int `json:"myName"`

// Field appears in JSON as key "myName" and
// the field is omitted from the object if its value is empty,
// as defined above.
Field int `json:"myName,omitempty"`

// Field appears in JSON as key "Field" (the default), but
// the field is skipped if empty.
// Note the leading comma.
Field int `json:",omitempty"`

// Field is ignored by this package.
Field int `json:"-"`

// Field appears in JSON as key "-".
Field int `json:"-,"`

又或者需要將輸出全部改為 String,Golang 也有提供此功能

Int64String int64 `json:",string"`

JSON 解碼
#

相較 JSON 編碼來說,Golang 對於未知的 JSON 實在是一大挑戰,一開始我們先從已知的結構來看

透過 Unmarshal 將 JSON 字串處理成對應的結構

func Unmarshal(data []byte, v interface{}) error

直接來看例子吧

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID int
    Name string
    Money float64
    Skills []string
    Relationship map[string]string
    Identification Identification
}

type Identification struct {
    Phone bool `json:"phone"`
    Email bool `json:"email"`
}

func main() {
    var jsonBlob = []byte(`{"ID":1,"Name":"Tony","Money":0,"Skills":["program","rich","play"],"Relationship":{"Dad":"Hulk","Mon":"Natasha"},"Identification":{"phone":true,"email":false}}`)
    
    var user User
    err := json.Unmarshal(jsonBlob, &user)
    if err != nil {
        fmt.Println("error:", err)
    }
    fmt.Printf("%+v", user)
}

解碼與編碼類似,但接受大小寫不同,不過如果欄位是私有的則不會配對

struct tag
#

如同在編碼的時候,解碼也有對應的 tag,就讓我們來看看吧

  • string tag 表示只有字串會被解析,當傳入的值不是字串時會報錯
...
type User struct {
    ID int
    Name string
    Money float64 `json:",string"`
    Skills []string
    Relationship map[string]string
    Identification Identification
}
...

正確:

"Money":"100.5"

錯誤:

"Money":100.5
error: json: invalid use of ,string struct tag, trying to unmarshal unquoted value into float64
  • - 表示不會被解析,但會給予初始值

動態解析 JSON
#

以上介紹的都是理想的狀況,但實際開發時會常常遇到未知的 JSON 格式,或是動態變化的結構,這時候就可以利用以下方法

interface & assertion
#

先利用 interface 來儲存任意的數據結構,再利用 assertion 的方式解析數據

map[string]interface{}

範例 a:interface 儲存輸入

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    var user map[string]interface{}
    var jsonString string = `{"ID":1, "Name":"Tony"}`
    if err := json.Unmarshal([]byte(jsonString), &user); err != nil {
        fmt.Println("ERROR:",err)
    }
    fmt.Printf("%#v\n", user)
}

範例 b:用 assertion 來解析數據

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID int
    Name string
    Money float64 `json:",string"`
    Skills []string
    Relationship map[string]string
    Identification Identification
    Career string
    Responsibility interface {}
}

type Identification struct {
    Phone bool `json:"phone"`
    Email bool `json:"email"`
}

func main() {
    var jsonBlob = []byte(`[
        {
            "ID":1,
            "Name":"Tony",
            "Career":"Engineer",
            "Responsibility":{
                "skill":"PHP&Golang&Network",
                "description":"coding"
            }
        },
        {
            "ID":2,
            "Name":"Jim",
            "Career":"Manager",
            "Responsibility":{
                "experienced":true
            }
        }
    ]`)
    
    var users []User
    if err := json.Unmarshal(jsonBlob, &users); err != nil {
        fmt.Println("error:", err)
    }
    fmt.Printf("%#v\n", users)
    fmt.Println(users[0].Responsibility.(map[string]interface{})["description"].(string))
    fmt.Println(users[1].Responsibility.(map[string]interface{})["experienced"].(bool))
}

雖然可利用上述方式,但是 code 實在不是很美觀,尤其是在讀出資料的時候

利用 *json.RawMessage 延遲解析
#

上述結構內的 Responsibility 實際上是在解析 Career 才會知道它的資料結構,這時候就可以使用 json.RawMessage 來延遲解析,讓數據以 byte 的方式繼續存在,等待下一次的解析

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID int
    Name string
    Money float64 `json:",string"`
    Skills []string
    Relationship map[string]string
    Identification Identification
    Career string
    Responsibility json.RawMessage
}

type Identification struct {
    Phone bool `json:"phone"`
    Email bool `json:"email"`
}

type Engineer struct {
    Skill string `json:"skill"`
    Description string `json:"description"`
}

type Manager struct {
    Experienced bool `json:"experienced"`
}

func main() {
    var jsonBlob = []byte(`[
        {
            "ID":1,
            "Name":"Tony",
            "Career":"Engineer",
            "Responsibility":{
                "skill":"PHP&Golang&Network",
                "description":"coding"
            }
        },
        {
            "ID":2,
            "Name":"Jim",
            "Career":"Manager",
            "Responsibility":{
                "experienced":true
            }
        }
    ]`)
    
    var users []User
    if err := json.Unmarshal(jsonBlob, &users); err != nil {
        fmt.Println("error:", err)
    }

    for _, user := range users {
        switch user.Career {
        case "Engineer":
            var responsibility Engineer
            if err := json.Unmarshal(user.Responsibility, &responsibility); err != nil {
                fmt.Println("error:", err)
            }
            fmt.Println(responsibility.Description)
        case "Manager":
            var responsibility Manager
            if err := json.Unmarshal(user.Responsibility, &responsibility); err != nil {
                fmt.Println("error:", err)
            }
            fmt.Println(responsibility.Experienced)
        default:
            fmt.Println("warning:", "don't exist")
        }
    }
}

相關文章

Go 介紹

·4 分鐘
在一年多以前,開始在社群跟朋友間很常聽到 Go 這個程式語言,也很好奇為什麼它突然聲名大噪,為什麼這麼多人推崇,在做了一些功課以後,近期有個機會可以將 Go 用在實際的專案上,就來稍微的敘述一下心得好了。

GPS 簡介

GPS 英文全名是:Global Positioning System,全球定位系統 一般來說要表示地理位置都會用座標來標示,實際上用座標是不夠清楚的,因為所指的座標是用什麼基準的並沒有說明。明確的地理位置會用大地基準(Datum)+坐標格式(Format/Grid)兩個參數來標示。

File Locking

再動手寫這篇之前,我踩到了一個雷,這篇描述了一下我遇到的問題跟解法,若是有人有更好的想法,或是我哪裡有搞錯了,請告訴我,我會非常感謝的!!!

HTTP Caching

近年來 Web 的服務越來越多樣化,其中不乏較大流量的服務,不僅增加傳輸的成本,也會拖慢 Browser 處理的時間,因此需要重複的利用之前所緩存的資源。

FFmpeg H.264 編碼器

·3 分鐘
H.264 / MPEG-4 AVC 是目前最被廣泛被應用的視訊編碼格式,它的壓縮效率比 MPEG-2、MPEG-4、RV40 …等舊視訊編碼格式還要高許多。