
在 Go 中,当结构体 B 嵌入结构体 A 时,若将 toJson() 方法定义在 *A 上,调用 b.toJson() 实际仍只序列化 A 的字段;需通过接口抽象或指针接收者适配,才能实现单个方法统一序列化嵌入后的完整结构。
在 go 中,当结构体 b 嵌入结构体 a 时,若将 `tojson()` 方法定义在 *a 上,调用 `b.tojson()` 实际仍只序列化 a 的字段;需通过接口抽象或指针接收者适配,才能实现单个方法统一序列化嵌入后的完整结构。
Go 的结构体嵌入(embedding)并非面向对象意义上的“继承”,而是一种组合语法糖:B 拥有 A 的字段和方法,但方法的接收者类型决定了其作用域。原代码中 func (object *A) toJson() 明确绑定到 *A 类型,即使 B 包含 A,Go 也不会自动将 B 的实例“提升”为 *A 来调用该方法——实际上,b.toJson() 能被调用,是因为 Go 允许通过嵌入字段自动代理 A 的方法,但代理时传入的是 &b.A(即 A 字段的地址),而非 &b 整体。因此 json.Marshal(&object) 中的 object 是 *A,自然只序列化 Foo。
✅ 正确解法不是简单地把方法移到 *B 上(如答案所示),因为这违背了“一个函数服务多个类型”的初衷;更通用、符合 Go 风格的方案是使用接口抽象序列化行为:
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
package main
import (
"encoding/json"
"fmt"
)
type A struct {
Foo string `json:"foo"`
}
type B struct {
A
Bar string `json:"bar"`
}
// 定义可序列化接口
type JSONer interface {
ToJSON() ([]byte, error)
}
// 为 A 实现(仅序列化自身字段)
func (a *A) ToJSON() ([]byte, error) {
return json.Marshal(a)
}
// 为 B 实现(序列化整个结构,含嵌入的 A)
func (b *B) ToJSON() ([]byte, error) {
return json.Marshal(b)
}
func main() {
b := B{A: A{Foo: "hello"}, Bar: "world"}
data, err := b.ToJSON()
if err != nil {
panic(err)
}
fmt.Println(string(data)) // 输出: {"foo":"hello","bar":"world"}
}⚠️ 注意事项:
- 若坚持“单一函数”,可借助
interface{}+ 类型断言,但会丧失类型安全与可读性,不推荐; - JSON 标签(如
`json:"foo"`)建议显式声明,避免默认小写导出问题; -
json.Marshal(&object)中的取地址操作在*A/*B接收者下是冗余的(因接收者已是指针),应直接json.Marshal(object); - 嵌入结构体的 JSON 序列化默认扁平展开(flattened),即
B的 JSON 包含A的字段(如foo)和B自身字段(如bar),无需额外配置。
总结:Go 中不存在传统继承的多态序列化,但通过接口约定 + 类型专属实现,既能保持代码复用性,又能精准控制序列化内容——这才是符合 Go “组合优于继承”哲学的优雅解法。

















