Golang中使用Alias方法对嵌套结构体进行JSON反序列化的异常行为
Golang中使用Alias方法对嵌套结构体进行JSON反序列化的异常行为
我偶然发现了这篇博客 Go 中的自定义 JSON 序列化 · Choly’s Blog,它使用别名(Alias)来反序列化一个结构体(并避免递归)……但当我尝试对嵌套结构体(它们有自己的 Unmarshal() 方法)使用类似的方法时,它就不再起作用了。请参见下面的代码。
看起来,“派生”结构体(BS)的别名被识别为 AS,因此调用了 AS.Unmarshal(),但后续的字段却没有被反序列化。我真的很想理解这是为什么。
我注意到,如果我在 type BS { AS; CS; Name string} 中包含另一个结构体 CS,并且 CS 有自己的 Unmarshal() 方法,代码就能正常工作。然而,如果 CS 没有自己的 Unmarshal() 方法,它就不会更新 CS 的值或 Name 字段。
package main
import (
"encoding/json"
"fmt"
)
type AS struct{ Id int }
func (as *AS) UnmarshalJSON(data []byte) error {
type Alias AS
aux := struct{ *Alias }{Alias: (*Alias)(as)}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
return nil
}
type BS struct {
AS
Name string
}
func (bs *BS) UnmarshalJSON(data []byte) error {
type Alias BS
aux := struct{ *Alias }{Alias: (*Alias)(bs)}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
return nil
}
func main() {
bs := BS{}
data := []byte(`{"id": 10, "name": "TEST ME"}`)
if err := json.Unmarshal(data, &bs); err != nil {
panic(err)
}
fmt.Printf("bs: %#v\n", bs)
// 没有 AS.UnmarshalJSON() 时的正确结果 - 例如通过更改方法名称
// 没有 AS.UnmarshalJSON() 时的输出:bs: main.BS{AS:main.AS{Id:10}, Name:"TEST ME"}
// 有 AS.UnmarshalJSON() 时的错误结果
// 输出:bs: main.BS{AS:main.AS{Id:10}, Name:""}
// 看起来它只调用了 AS.UnmarshalJSON(),后续字段没有被反序列化
}
我真的很想理解这是为什么?
更多关于Golang中使用Alias方法对嵌套结构体进行JSON反序列化的异常行为的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang中使用Alias方法对嵌套结构体进行JSON反序列化的异常行为的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
这个问题涉及到Go语言JSON反序列化中方法集解析和结构体嵌入的交互。当BS嵌入了AS,而两者都有UnmarshalJSON方法时,会出现方法集冲突。
关键原因在于:BS的UnmarshalJSON方法中,aux结构体使用了*Alias类型,而Alias是BS的别名。由于BS嵌入了AS,*Alias类型会继承AS的UnmarshalJSON方法,导致JSON包在反序列化aux时直接调用了AS.UnmarshalJSON,跳过了BS字段的常规反序列化。
以下是修复后的代码示例:
package main
import (
"encoding/json"
"fmt"
)
type AS struct{ Id int }
func (as *AS) UnmarshalJSON(data []byte) error {
type Alias AS
aux := &struct {
*Alias
}{
Alias: (*Alias)(as),
}
if err := json.Unmarshal(data, aux); err != nil {
return err
}
return nil
}
type BS struct {
AS
Name string
}
func (bs *BS) UnmarshalJSON(data []byte) error {
// 使用临时结构体避免继承AS的UnmarshalJSON方法
aux := &struct {
AS `json:",inline"`
Name string `json:"name"`
}{}
if err := json.Unmarshal(data, aux); err != nil {
return err
}
bs.AS = aux.AS
bs.Name = aux.Name
return nil
}
func main() {
bs := BS{}
data := []byte(`{"id": 10, "name": "TEST ME"}`)
if err := json.Unmarshal(data, &bs); err != nil {
panic(err)
}
fmt.Printf("bs: %#v\n", bs)
// 输出: bs: main.BS{AS:main.AS{Id:10}, Name:"TEST ME"}
}
或者,如果希望保持原有的别名模式,可以显式地处理嵌入字段:
func (bs *BS) UnmarshalJSON(data []byte) error {
type Alias BS
aux := &struct {
*Alias
// 显式声明AS字段,避免继承方法
AS json.RawMessage `json:"as"`
}{
Alias: (*Alias)(bs),
}
if err := json.Unmarshal(data, aux); err != nil {
return err
}
// 手动处理AS字段
if err := json.Unmarshal(aux.AS, &bs.AS); err != nil {
return err
}
return nil
}
问题的本质是:当结构体嵌入另一个有UnmarshalJSON方法的结构体时,JSON包会优先调用嵌入结构体的方法。在别名方法中,由于类型转换保留了方法集,导致AS.UnmarshalJSON被意外调用。

