为何给指针接收器定义自定义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
相关产品推荐
相关产品推荐

