Golang 中的json.Marshal問題總結(推薦)
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內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Go語言中序列化與反序列化示例詳解
- go語言中的json與map相互轉換實現
- golang中json小談之字符串轉浮點數的操作
- golang中json操作的完全指南
- 在Go中使用JSON(附demo)