在處理 JSON 數據時,我們通常會使用 json.Unmarshal 函數將 JSON 字符串解析為 Go 語言中的結構體。然而,在 UnmarshalJSON 函數內部調用 json.Unmarshal 函數可能會導致堆棧溢出的錯誤。這是因為 UnmarshalJSON 函數在解析 JSON 數據時會再次調用自身,從而導致無限循環。為了避免這種情況,我們可以使用 json.Decoder 的 Decode 方法來解析 JSON 數據,而不是直接調用 json.Unmarshal 函數。這樣做可以確保不會造成堆棧溢出的問題,保證代碼的健壯性和性能。
問題內容
我想執行一些額外的步驟來初始化我的實現 UnmarshalJSON
中的數據結構。在該實現中調用 json.Unmarshal(b, type)
自然會導致堆棧溢出。
JSON 解碼器不斷嘗試查找是否有自定義 UnmarshalJSON
實現,然后再次調用 json.Unmarshal
。
還有其他方法可以做到這一點嗎?只需調用底層默認實現就不會導致此問題?
解決方法
避免這種情況/防止這種情況的一種簡單而常見的方法是使用 type
關鍵字,并使用類型 conversion 來傳遞該類型的值(該值可以如果是您的原始值,則可以進行類型轉換,因為新類型將原始類型作為其基礎類型)。
這是有效的,因為 type
關鍵字創建了一個新類型,并且新類型將具有零個方法(它不會“繼承”基礎類型的方法)。
這會產生一些運行時開銷嗎?否。引用自 規范:轉換:
讓我們看一個例子。我們有一個帶有數字 Age
的 Person
類型,并且我們要確保 Age
不能為負數(小于 0
)。
type Person struct { Name string `json:"name"` Age int `json:"age"` } func (p *Person) UnmarshalJSON(data []byte) error { type person2 Person if err := json.Unmarshal(data, (*person2)(p)); err != nil { return err } // Post-processing after unmarshaling: if p.Age < 0 { p.Age = 0 } return nil }
登錄后復制
測試它:
var p *Person fmt.Println(json.Unmarshal([]byte(`{"name":"Bob","age":10}`), &p)) fmt.Println(p) fmt.Println(json.Unmarshal([]byte(`{"name":"Bob","age":-1}`), &p)) fmt.Println(p)
登錄后復制
輸出(在 Go Playground 上嘗試):
&{Bob 10} &{Bob 0}
登錄后復制
當然,相同的技術也適用于自定義封送處理(MarshalJSON()
):
func (p *Person) MarshalJSON() ([]byte, error) { // Pre-processing before marshaling: if p.Age < 0 { p.Age = 0 } type person2 Person return json.Marshal((*person2)(p)) }
登錄后復制
測試它:
p = &Person{"Bob", 10} fmt.Println(json.NewEncoder(os.Stdout).Encode(p)) p = &Person{"Bob", -1} fmt.Println(json.NewEncoder(os.Stdout).Encode(p))
登錄后復制
輸出(在同一個 Go Playground 示例中):
{"name":"Bob","age":10} {"name":"Bob","age":0}
登錄后復制
一個非常相似的問題是,當您為 fmt
的自定義文本表示定義 String() string
方法時a> 包,并且您想要使用您修改的默認字符串表示形式。在這里閱讀更多相關信息:t 和 *t 之間的區別