iT邦幫忙

2023 iThome 鐵人賽

DAY 28
0
自我挑戰組

Go in 3o系列 第 28

[Day28] Go in 30 - 處理JSON資料

  • 分享至 

  • xImage
  •  

一、本篇提要

本篇重點會著重於Go如何解析JSON,以及了解Go自有的 gob 二進位編碼功能。

  • 解碼JSON為Go結構
  • 將Go結構編碼為JSON

二、解碼JSON為Go結構

使用 Go 標準函式庫 encoding/json Unmarshal()函式

2.1 Unmarshal() 函式

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

參數 data([]byte 切片)就是儲存 JSON 資料的字串,
而 v 就是用來儲存解析結果的變數,它型別為空介面,我們會傳入一個結構指標。

Unmarshal()會解析JSON字串並嘗試存入到該結構中, v 不能為 nil,否則會跳出錯誤 :

call Unmarshal passes non-pointer as second argument

範例 :

package main

import (
	"encoding/json"
	"fmt"
)

type greeting struct { //用來儲存解碼的JSON資料的結構
    Message string
}

func main() {
    //JSON
    data := []byte(`
    
    {
    "message":"Hello Gopher !"
    }
    
    `)
    
    var v greeting // 建立一個空結構
    err := json.Unmarshal(data, &v) // 解析 JSON 和寫入 v
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(v) //印出c內容
}

執行結果:
變成greeting結構
https://ithelp.ithome.com.tw/upload/images/20231006/20162693ZT9ATias7E.png

結構的欄位必須是可以匯出的(exportable),也就是字首英文大寫,才能被Unmarshal()看到!!

2.2 加上結構標籤

當我們希望將 JSON 資料解碼到 Go 的結構時,我們可以使用結構標籤(struct tags)來告知 Unmarshal 函數如何將 JSON 中的 key 映射到結構的字段。這在 JSON 的 key 名稱和 Go 結構的字段名稱不一致時特別有用。

package main

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

type Person struct {
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
	Age       int    `json:"age"`
}

func main() {
	data := []byte(`{
		"first_name": "John",
		"last_name": "Doe",
		"age": 30
	}`)

    if !json.Valid(data) {
        fmt.Printf("JSON格式無效: %s", data)
        os.Exit(1)
    }


	var p Person
	if err := json.Unmarshal(data, &p); err != nil {
		fmt.Println("Error:", err)
		return
	}
	fmt.Printf("Decoded struct: %+v\n", p)
}

在上述範例中,Person 結構有三個字段,每個字段後面都有結構標籤。這些標籤指示 json.Unmarshal 函數如何將 JSON 資料的 key 映射到結構的對應字段。

執行結果:

Decoded struct: {FirstName:John LastName:Doe Age:30}

Unmarshal()會根據以下規則來決定要把JSON的鍵配對到哪一個結構欄位 :

  • 某個可匯出欄位的標籤值(tag)對應到JSON鍵
  • 某個可匯出欄位的名稱,本身有對應到JSON鍵(大小寫可以不同)
  • 若找不到,值就不會放進結構任何欄位

2.3 解碼 JSON 到複合結構

一個 JSON 複合結構 :

{
   "lname":"Ricky",
   "fname":"Hsieh",
   "address": {
       "street" : "ABC st",
       "city" : "Taipei",
       "state" : "TW"
   }
}

同樣的我們需要複合struct :

package main

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

type Person struct {
	FirstName string `json:"fname"`
	LastName  string `json:"lname"`
	Address   address `json:"address"` //子結構
}

type address struct {
	Street string `json:"street"`
	City  string `json:"city"`
	State string `json:"state"` //子結構
}

func main() {
	data := []byte(`{
   "lname":"Ricky",
   "fname":"Hsieh",
   "address": {
       "street" : "ABC st",
       "city" : "Taipei",
       "state" : "TW"
   }
}`)

    if !json.Valid(data) {
        fmt.Printf("JSON格式無效: %s", data)
        os.Exit(1)
    }


	var p Person
	if err := json.Unmarshal(data, &p); err != nil {
		fmt.Println("Error:", err)
		return
	}
	fmt.Printf("Decoded struct: %+v\n", p)
}

執行結果 :

https://ithelp.ithome.com.tw/upload/images/20231006/20162693Vv6RvdUD3J.png

以下是一個更複雜的 JSON 範例,其中包含了一個 contacts 陣列(slice),每一個聯絡人都有姓名和地址等資料:

{
   "company": "TechCorp",
   "contacts": [
      {
         "lname": "Wang",
         "fname": "Li",
         "address": {
            "street": "123 Main St",
            "city": "Beijing",
            "state": "BJ"
         }
      },
      {
         "lname": "Chen",
         "fname": "Yu",
         "address": {
            "street": "456 West St",
            "city": "Shanghai",
            "state": "SH"
         }
      }
   ]
}

要解析上述 JSON,需要擴展 Go 程式碼來包含這個新的結構:

type Company struct {
	Name     string   `json:"company"`
	Contacts []Person `json:"contacts"`
}

完整程式碼一覽 :

package main

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

type Person struct {
	FirstName string `json:"fname"`
	LastName  string `json:"lname"`
	Address   address `json:"address"` //子結構
}

type Company struct {
	Name     string   `json:"company"`
	Contacts []Person `json:"contacts"`
}

type address struct {
	Street string `json:"street"`
	City  string `json:"city"`
	State string `json:"state"` //子結構
}

func main() {
	data := []byte(`{
        "company": "TechCorp",
        "contacts": [
           {
              "lname": "Wang",
              "fname": "Li",
              "address": {
                 "street": "123 Main St",
                 "city": "Beijing",
                 "state": "BJ"
              }
           },
           {
              "lname": "Chen",
              "fname": "Yu",
              "address": {
                 "street": "456 West St",
                 "city": "Shanghai",
                 "state": "SH"
              }
           }
        ]
     }
     `)

    if !json.Valid(data) {
        fmt.Printf("JSON格式無效: %s", data)
        os.Exit(1)
    }


	var c Company
	if err := json.Unmarshal(data, &c); err != nil {
		fmt.Println("Error:", err)
		return
	}
	fmt.Printf("Decoded struct: %+v\n", c)
}

https://ithelp.ithome.com.tw/upload/images/20231006/20162693Tfy9TJRYJq.png

三、將Go結構編碼為JSON

現在我們要將Go結構轉為JSON。

3.1 使用 encoding/json 套件中 Marshal()函式

Marshal()功用就是和Unmarshal()相反

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

參數 v 需要編碼成JSON格式的原始資料,通常是個結構。
Marshal()會回傳JSON字串([]byte切片)以及error值,如果編碼失敗 error 就不為nil。

簡單的範例 :

package main

import (
	"encoding/json"
	"fmt"
)

// 定義一個 Person 結構
type Person struct {
	FirstName string `json:"fname"`
	LastName  string `json:"lname"`
	Age       int    `json:"age"`
}

func main() {
	// 創建一個 Person 實例
	p := Person{
		FirstName: "John",
		LastName:  "Doe",
		Age:       30,
	}

	// 使用 Marshal 函式將 Person 結構轉為 JSON 格式
	jsonData, err := json.Marshal(p)
	if err != nil {
		fmt.Println("Error marshaling:", err)
		return
	}

	// 輸出 JSON 數據
	fmt.Println(string(jsonData))
}

輸出結果 :

{"fname":"John","lname":"Doe","age":30}

3.2 Marshal()解結構規則

json輸出結果太亂可以用 : Json Formatter

Marshal() 的解析結構規則,會遵循以下來產生JSON鍵值對 :

  • 只有可匯出欄位才能被加入為JSON鍵。
  • 帶有JSON標籤的欄位才會被加入,其他的忽略。
  • 如果欄位只有一個,那麼不管有沒有JSON標籤都會被加入。
  • 如果結構有多重欄位,但都沒有JSON標籤,那麼會全數忽略(且不產生錯誤)

3.3 將多重欄位結構轉JSON

來看以下範例 :

package main

import (
	"encoding/json"
	"fmt"
)

// Person 結構,其中包含一個 Address 欄位結構
type Person struct {
	FirstName string  `json:"fname"`
	LastName  string  `json:"lname"`
	Age       int     `json:"age"`
	Addresses []Address `json:"addresses"` // 注意這是一個切片,代表一個人可能有多個地址
}

// Address 結構,表示地址
type Address struct {
	Street  string `json:"street"`
	City    string `json:"city"`
	State   string `json:"state"`
	ZipCode string `json:"zip"`
}

func main() {
	// 創建一個 Person 實例,其中包含兩個地址
	p := Person{
		FirstName: "John",
		LastName:  "Doe",
		Age:       30,
		Addresses: []Address{
			{
				Street:  "123 Elm St",
				City:    "Somewhere",
				State:   "CA",
				ZipCode: "90001",
			},
			{
				Street:  "456 Oak St",
				City:    "Nowhere",
				State:   "TX",
				ZipCode: "75001",
			},
		},
	}

	// 使用 Marshal 函式將 Person 結構轉為 JSON 格式
	jsonData, err := json.Marshal(p)
	if err != nil {
		fmt.Println("Error marshaling:", err)
		return
	}

	// 輸出 JSON 數據
	fmt.Println(string(jsonData))
}

輸出結果 :

{
  "fname": "John",
  "lname": "Doe",
  "age": 30,
  "addresses": [
    {
      "street": "123 Elm St",
      "city": "Somewhere",
      "state": "CA",
      "zip": "90001"
    },
    {
      "street": "456 Oak St",
      "city": "Nowhere",
      "state": "TX",
      "zip": "75001"
    }
  ]
}

如果我們沒有JSON資料直接值型程式 :

{
  "fname": "",
  "lname": "",
  "age": 0,
  "addresses": null
}

3.4 略過欄位

如果希望某個欄位沒有賦值就不要編碼到JSON資料中,就再欄位標籤後再加上omitempty

package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	FirstName string   `json:"fname"`
	LastName  string   `json:"lname"`
	Age       int      `json:"age,omitempty"`     // 使用 omitempty,如果 Age 為0,則不會被編碼到 JSON 中
	Email     string   `json:"email,omitempty"`   // 使用 omitempty,如果 Email 為空字符串,則不會被編碼到 JSON 中
	Addresses []Address `json:"addresses,omitempty"` // 使用 omitempty,如果 Addresses 為空切片或 nil,則不會被編碼到 JSON 中
}

type Address struct {
	Street  string `json:"street"`
	City    string `json:"city"`
	State   string `json:"state"`
	ZipCode string `json:"zip,omitempty"` // 使用 omitempty,如果 ZipCode 為空字符串,則不會被編碼到 JSON 中
}

func main() {
	// 這個例子中,Age 和 Email 欄位都沒有賦值
	p := Person{
		FirstName: "John",
		LastName:  "Doe",
		Addresses: []Address{
			{
				Street: "123 Elm St",
				City:   "Somewhere",
				State:  "CA",
			},
		},
	}

	jsonData, err := json.Marshal(p)
	if err != nil {
		fmt.Println("Error marshaling:", err)
		return
	}

	fmt.Println(string(jsonData))
}

當你執行這段代碼時,Age 和 Email 欄位不會出現在 JSON 輸出中,因為它們沒有賦值,而且我們使用了 omitempty 標籤。同樣,由於 ZipCode 在 Address 結構中沒有賦值,所以它也不會出現在 JSON 輸出中。

執行結果 :

{
  "fname": "John",
  "lname": "Doe",
  "addresses": [
    {
      "street": "123 Elm St",
      "city": "Somewhere",
      "state": "CA"
    }
  ]
}

!!!注意事項!!!

使用omitempty時,會緊接著接在標籤逗點後面(age,omitempty),要是多一個空格(age, omitempty),

現在,Age 欄位的標籤之間有一個額外的空格。但要注意,增加這個空格可能會導致 omitempty 標籤不起作用。這是因為 Go 的 encoding/json 套件期望標籤中的選項之間沒有額外的空格。

**這樣甚至不會傳回非nil的error值,除非使用 go vet 工具來檢查,因此使用omitempty須注意標籤格式。

3.5 其他標籤效果

type book struct {
    ISBN string `json:"-"` //短折線
    Title string `json:"title"`
    YearPublished string `json:"yearpub,omitempty"`
    Author string `json:""`  //沒有鍵名稱
    CoAuthor string `json:`,omitempty   //沒有鍵名稱
}
  • 短折線代表 : 直接略過
  • 沒有鍵名 : 沿用欄位名稱

3.6 有排版的JSON編碼結果

可以使用 MarshalIndent()函式,它的作用跟Marshall()幾乎一樣,只差結果會幫你縮排和換行 :

func MarshalIndent(v interface{}, prefix, ident string) ([]byte, error)

比起 Marshal(),MarshalIndent()函式多了兩個參數:

  • prefix 是要放在每一行開頭的前綴詞,這裡我們不會使用,傳入空字串即可。
  • indent 縮排文字,例如空幾格或其他字元。

範例 :

package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	FirstName string   `json:"fname"`
	LastName  string   `json:"lname"`
	Age       int      `json:"age,omitempty"`
	Email     string   `json:"email,omitempty"`
	Addresses []Address `json:"addresses,omitempty"`
}

type Address struct {
	Street  string `json:"street"`
	City    string `json:"city"`
	State   string `json:"state"`
	ZipCode string `json:"zip,omitempty"`
}

func main() {
	p := Person{
		FirstName: "John",
		LastName:  "Doe",
		Age:       30,
		Email:     "johndoe@example.com",
		Addresses: []Address{
			{
				Street:  "123 Elm St",
				City:    "Somewhere",
				State:   "CA",
				ZipCode: "12345",
			},
		},
	}

	// 使用 MarshalIndent 函式,以「\t」作為縮排
	jsonData, err := json.MarshalIndent(p, "", "\t")
	if err != nil {
		fmt.Println("Error marshaling:", err)
		return
	}

	fmt.Println(string(jsonData))
}

以上就是簡單的如何處理JSON編碼的介紹~


上一篇
[Day27] Go in 30 - 時間處理
下一篇
[Day29] Go in 30 - 系統與檔案 - flag 與 signals
系列文
Go in 3o30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言