首頁 > 軟體

Golang 中的json.Marshal問題總結(推薦)

2022-06-27 18:01:18

1.Quiz

有如下一個例子:

package main

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

type RecordBrief struct {
	time.Time
	ID int
}

func main() {
	r := RecordBrief{
		Time: time.Now(),
		ID:   6,
	}
	m, _ := json.MarshalIndent(r, "", "t")
	fmt.Println(string(m))
}

你期望的結果是像:

{
        "Time": "2022-06-25T10:49:39.597537249+08:00",
        "ID": 6
}

還是:

{
        "ID": 6
}

或者是別的?

2.Answer

其實如果你認為的答案不是:

"2022-06-25T10:52:23.590933959+08:00"

也沒能想明白原因,可以繼續往下看看。

3.Resolving

誠然,我們在學習json的序列化和反序列化的時候,目的就是把一個Golang struct值序列化為對應的json string罷了。可能我們還知道一些Marshal的規則,比如struct的欄位需要定義為可匯出的,比如還可通過定義對應的json tag來修改struct field對應的json欄位名稱等。

但是對於json.Marshal函數的細節可能大家不會去太在意。本次提出的問題中,我們不難注意到其中的time.Time是一個匿名(Anonymous)欄位,而這個就是答案的由來。我們先看看json.Marshal的註釋檔案中的一個解釋:

// ...
// Marshal traverses the value v recursively.
// If an encountered value implements the Marshaler interface
// and is not a nil pointer, Marshal calls its MarshalJSON method
// to produce JSON. If no MarshalJSON method is present but the
// value implements encoding.TextMarshaler instead, Marshal calls
// its MarshalText method and encodes the result as a JSON string.
// ...
func Marshal(v interface{}) ([]byte, error) {
	...
}

Marshal函數遞迴地遍歷傳入的序列化物件v(及其成員)。當面對一個實現了json.Marshaler介面的物件(不能是一個空指標)時,Marshal函數就會呼叫該物件的MarshalJSON方法來生成JSON內容。如果沒有實現json.Marshaler,而是實現了encoding.TextMarshaler介面,那麼就會呼叫它的MarshalText方法,然後把該方法返回的結果轉編為一個JSON字串。

然後我們再看看time.Time

type Time struct {
	...
}

// MarshalJSON implements the json.Marshaler interface.
// The time is a quoted string in RFC 3339 format, with sub-second precision added if present.
func (t Time) MarshalJSON() ([]byte, error) {
	if y := t.Year(); y < 0 || y >= 10000 {
		// RFC 3339 is clear that years are 4 digits exactly.
		// See golang.org/issue/4556#c15 for more discussion.
		return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]")
	}

	b := make([]byte, 0, len(RFC3339Nano)+2)
	b = append(b, '"')
	b = t.AppendFormat(b, RFC3339Nano)
	b = append(b, '"')
	return b, nil
}

所以time.Time是實現了json.Marshaler介面的。然後觀察到它的實現是把時間按照RFC3339Nano格式字串值返回為json序列化結果,這和我們實際上執行程式看到的結果是一致的。

那麼再看看我們的type定義:

type RecordBrief struct {
	time.Time
	ID int
}

為什麼ID欄位不見了?正是因為匿名欄位的原因,Golang中的這種用法有點類似於繼承,所以RecordBrief型別也自動具有了time.Time的所有方法,當然也包括了MarshalJSON,從而也就實現了json.Marshaler介面。如此一來,當一個RecordBrief被Marshal的時候,它的序列化結果就被time.Time的序列化結果給覆蓋了。

Conclusion

如果你和我一樣,沒能一下知道原因,那多半是對一些常見的知識瞭解的深度和廣度不夠。我之前確實不知道time.Time居然也實現了json.Marshaler介面,也不清楚json.Marshal到底在做什麼,所以不知道答案也就理所當然了,後來經過閱讀檔案註釋,才終於對該問題有了一些認知(後續應該總結一篇json.Marshal的原始碼解析)。

至此,如果我們想要這種樣子的結果:

{
        "Time": "2022-06-25T10:49:39.597537249+08:00",
        "ID": 6
}

最簡單的方式是修改struct,將time.Time作為一個非匿名的匯出欄位:

type RecordBrief struct {
	Time time.Time
	ID int
}

另一種方法是給我們的RecordBrief實現json.Marshaler介面:

type RecordBrief struct {
	time.Time
	ID int
}
func (r RecordBrief) MarshalJSON() ([]byte, error) {
	//非常簡單的一種方式就是建立中間型別
	t := struct {
		Time time.Time
		ID   int
	}{
		r.Time,
		r.ID,
	}
	return json.Marshal(t)
}

到此這篇關於Golang 中的json.Marshal問題總結的文章就介紹到這了,更多相關Golang json.Marshal內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com