You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何给指针接收器定义自定义MarshalJSON方法时,传入[]T和&[]T均能生效?

为何给指针接收器定义自定义MarshalJSON方法时,传入[]T和&[]T均能生效?

你观察到的现象确实容易让人困惑,核心原因不是单个book值不可寻址(它其实是可寻址的),而是Go的json包在处理单个值和切片元素时,遵循了不同的序列化规则。咱们一步步拆解:

1. 先明确方法集的基础规则

在Go中,你只为*Book类型定义了MarshalJSON方法,所以:

  • Book类型的方法集不包含MarshalJSON,因此直接传递Book值给json.Marshal时,会用默认的结构体序列化逻辑(对应你代码中的变量a的输出)。
  • *Book类型的方法集包含MarshalJSON,因此传递&Book时,会调用你的自定义序列化逻辑(对应变量b的输出)。

2. 切片元素的特殊序列化逻辑

当json.Marshal处理切片(不管你传的是[]Book还是&[]Book)时,会遍历切片中的每个元素,并对元素执行以下检查:

如果元素是可寻址的,且元素的指针类型(这里是*Book)实现了json.Marshaler接口,那么json包会自动获取元素的地址,调用指针类型的MarshalJSON方法来序列化该元素。

而切片的元素天生是可寻址的(你可以通过&books[0]直接获取元素的内存地址),所以每个Book元素都会被自动以*Book的方式处理,这就是为什么变量c和d的输出都是自定义序列化的结果。

3. 单个值和切片元素的关键差异

你可能会疑惑:单个book变量也是可寻址的(&book完全合法),为什么json.Marshal(book)没有触发自定义方法?

这是因为json包对单个值的处理逻辑和切片元素不同:

  • 对于单个值,json.Marshal只会检查你传递的直接类型是否实现了json.Marshaler。如果没有,就直接用默认序列化,不会自动为你取地址去检查指针类型的方法集——你必须显式传递指针(比如&book)才会触发自定义方法。
  • 对于切片元素,json包会额外做一层检查:为了支持更灵活的序列化,它会利用元素的可寻址性,自动尝试用指针类型的方法(如果存在的话)。

结合你的代码验证

咱们再对应到你的代码输出:

  • a:json.Marshal(book) → Book类型无MarshalJSON,默认序列化Author的两个字段,输出{"author":{"first_name":"Astrid","last_name":"Lindgren"}}。
  • b:json.Marshal(&book) → *Book有自定义方法,合并作者名,输出{"author":"Astrid Lindgren"}。
  • c:json.Marshal(books) → 遍历每个可寻址的Book元素,自动用*Book的方法序列化,输出[{"author":"Astrid Lindgren"}]。
  • d:json.Marshal(&books) → 本质还是序列化切片本身,遍历元素的逻辑和c完全一致,所以输出和c相同。

总结你的疑问

你猜测“切片里的值可寻址而单个值book不可寻址”是不准确的——单个book变量是可寻址的。真正的区别在于json包对单个值和切片元素的序列化策略:切片元素会自动利用可寻址性来调用指针类型的自定义方法,而单个值则需要你显式传递指针才会触发。


你的示例代码

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	book := Book{
		Author: Author{
			FirstName: "Astrid",
			LastName:  "Lindgren",
		},
	}

	books := []Book{}
	books = append(books, book)

	a, _ := json.Marshal(book)
	fmt.Println(string(a)) // {"author":{"first_name":"Astrid","last_name":"Lindgren"}}

	b, _ := json.Marshal(&book)
	fmt.Println(string(b)) // {"author":"Astrid Lindgren"}

	c, _ := json.Marshal(books)
	fmt.Println(string(c)) // [{"author":"Astrid Lindgren"}]

	d, _ := json.Marshal(&books)
	fmt.Println(string(d)) // [{"author":"Astrid Lindgren"}]
}

type Book struct {
	Author Author `json:"author"`
}

type Author struct {
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
}

type BookJSON struct {
	Author string `json:"author"`
}

func (r *Book) ToJSONModel() BookJSON {
	return BookJSON{
		Author: r.Author.FirstName + " " + r.Author.LastName,
	}
}

func (r *Book) MarshalJSON() ([]byte, error) {
	return json.Marshal(r.ToJSONModel())
}

备注:内容来源于stack exchange,提问作者mugraph

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.20 10:23:05