Golang接口签名与内部库接口冲突导致栈溢出问题
Golang接口签名与内部库接口冲突导致栈溢出问题
以下示例代码会导致栈溢出,因为其接口签名与 encoding/json/Marshaler 相同。
json.Marshal(s) 会在内部调用 MarshalJSON,因此最终会形成递归函数调用。
这是设计使然还是设计失误?
package main
import (
"encoding/json"
"fmt"
)
type IJSON interface {
MarshalJSON() ([]byte, error) // 此接口签名与 "encoding/json/Marshaler" 相同
}
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func (s *Student) MarshalJSON() ([]byte, error) {
return json.Marshal(s)
}
func main() {
s := &Student{"James", 12}
data, err := s.MarshalJSON()
if err != nil {
panic(err)
}
fmt.Printf("%s", data)
}
更多关于Golang接口签名与内部库接口冲突导致栈溢出问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html
这是一个非常特殊的例子,之所以会出现这种情况是因为编码器使用了反射机制。这种情况并不具有普遍性。
请注意 encoding/json 包并不属于 stdlib。
在 Go 语言中,我们使用包来防止命名冲突。
请记住 Go 语言的接口是隐式实现的。既不需要显式声明,也没有 “implements” 关键字。
MarshalJSON 用于希望覆盖默认行为的类型。这个方法工作得很好:
package main
import (
"encoding/json"
"fmt"
)
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
if data, err := json.Marshal(Student{"James", 12}); err != nil {
panic(err)
} else {
fmt.Printf("%s\n", data)
}
}
你好 Charles,
创建"IJSON"是一个设计错误 🙂
你的接口与"encoding/json/Marshaler"的公共方法冲突
在内部,encode/json 使用反射来确定使用哪个编码器。
通过声明一个方法 func (s *Student) MarshalJSON() ([]byte, error),你的 Student 就符合了 json.Marshaler 接口。
因此编码器会使用你的 MarshalJSON() 方法,结果变成了无限递归调用。
Go 的接口系统是宽松的。
此致,
这是一个典型的递归调用导致的栈溢出问题,这是设计使然而非设计失误。encoding/json包在设计时明确规定了Marshaler接口的行为模式。
问题出现在Student.MarshalJSON方法的实现中:当调用json.Marshal(s)时,JSON包检测到该类型实现了Marshaler接口,于是再次调用MarshalJSON方法,从而形成无限递归。
正确的实现应该避免这种递归调用。以下是几种解决方案:
方案1:使用中间类型避免递归
func (s *Student) MarshalJSON() ([]byte, error) {
type Alias Student
return json.Marshal(&struct {
*Alias
}{
Alias: (*Alias)(s),
})
}
方案2:手动构建JSON
func (s *Student) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`{"name":"%s","age":%d}`, s.Name, s.Age)), nil
}
方案3:使用map构建
func (s *Student) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": s.Name,
"age": s.Age,
})
}
第一种方案是最常用的,它通过类型别名避免了递归调用,同时保持了原有的JSON标签和结构。这种设计模式在需要自定义JSON序列化逻辑时非常有用,比如添加额外字段或修改字段格式:
func (s *Student) MarshalJSON() ([]byte, error) {
type Alias Student
return json.Marshal(&struct {
*Alias
DisplayName string `json:"display_name"`
}{
Alias: (*Alias)(s),
DisplayName: fmt.Sprintf("%s (%d)", s.Name, s.Age),
})
}
这种接口设计确保了类型可以完全控制自己的序列化过程,但需要开发者正确实现以避免递归问题。


