关于go:为什么json.Unmarshal为什么使用引用而不使用指针?
type ShortTranscodeEntity struct {
Weight int `json:"weight"` // 任务权重
TaskId string `json:"taskId"` //任务id
TaskName string `json:"taskName"` //视频标题
TransType int `json:"transType"` //转码类型 0 轮播 1点播
Map map[string]int `json:"map"`
Arr []int `json:"arr"`
}
var shortMark = `{
"weight": 100,
"taskId": "task001",
"taskName": "example video",
"streamId": "stream001"
}`
func Test_Unmarshal(t *testing.T) {
var f ShortTranscodeEntity
t.Log(f) // {0 0}
t.Log(&f) // &{0 0}
t.Logf("&f的类型 %T &f占用的字节数是 %d", &f, unsafe.Sizeof(&f)) // &f的类型 *feature.ShortTranscodeEntity &f占用的字节数是 8
f.Map["1"] = 1
f.Arr = append(f.Arr, 1)
err := json.Unmarshal([]byte(shortMark), &f)
if err != nil {
t.Logf("unmarshal f have some err:%v", err)
return
}
t.Log(f) // {100 task001 example video 0}
var pf *ShortTranscodeEntity
t.Log(pf) // <nil>
t.Logf("pf的类型 %T pf占用的字节数是 %d", pf, unsafe.Sizeof(pf)) // pf的类型 *feature.ShortTranscodeEntity pf占用的字节数是 8
err = json.Unmarshal([]byte(shortMark), pf)
if err != nil {
t.Logf("unmarshal pf have some err:%v", err)
return
}
t.Log(pf)
}
现象
定义指针 unmarshal 时,出现 json: Unmarshal(nil *feature.ShortTranscodeEntity)
错误。
原因
指针初始化值为 nil, 结构体初始化为基础值。
json.unmarshal源码中
func (d *decodeState) unmarshal(v any) error
// 当 v 为 nil 是,返回 Value{}, 此时
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Pointer || rv.IsNil() {
return &InvalidUnmarshalError{reflect.TypeOf(v)}
}
d.scan.reset()
d.scanWhile(scanSkipSpace)
// We decode rv not rv.Elem because the Unmarshaler interface
// test must be applied at the top level of the value.
err := d.value(rv)
if err != nil {
return d.addErrorContext(err)
}
return d.savedError
}
// ValueOf 函数返回一个新的 Value,该 Value 初始化为接口 i 中存储的具体值。
// ValueOf(nil) 返回零值 Value。
// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i any) Value {
if i == nil {
return Value{}
}
// TODO: Maybe allow contents of a Value to live on the stack.
// For now we make the contents always escape to the heap. It
// makes life easier in a few places (see chanrecv/mapassign
// comment below).
escapes(i)
return unpackEface(i)
}
// IsNil reports whether its argument v is nil. The argument must be
// a chan, func, interface, map, pointer, or slice value; if it is
// not, IsNil panics. Note that IsNil is not always equivalent to a
// regular comparison with nil in Go. For example, if v was created
// by calling ValueOf with an uninitialized interface variable i,
// i==nil will be true but v.IsNil will panic as v will be the zero
// Value.
// IsNil报告其参数v是否为nil。
// 参数必须是chan、func、interface、map、pointer或slice值;否则,IsNil将会panic。
// 请注意,IsNil并不总是等价于在Go中与nil进行常规比较。
// 例如,如果v是通过调用ValueOf并传入未初始化的接口变量i创建的,那么i==nil将为true,但v.IsNil将会panic,因为v是零值。
func (v Value) IsNil() bool {
k := v.kind()
switch k {
case Chan, Func, Map, Pointer, UnsafePointer:
if v.flag&flagMethod != 0 {
return false
}
ptr := v.ptr
if v.flag&flagIndir != 0 {
ptr = *(*unsafe.Pointer)(ptr)
}
return ptr == nil
case Interface, Slice:
// Both interface and slice are nil if first word is 0.
// Both are always bigger than a word; assume flagIndir.
return *(*unsafe.Pointer)(v.ptr) == nil
}
panic(&ValueError{"reflect.Value.IsNil", v.kind()})
}
因为指针初始化为 nil 所以返回 json: Unmarshal(nil *feature.ShortTranscodeEntity)
错误
arr map 呢?
func Test_Unmarshal_slice(t *testing.T) {
markArr := `[1,2,3]`
var arr []int
t.Log(arr) // []
t.Log(&arr) // &[]
t.Logf("&arr的类型 %T &arr占用的字节数是 %d", &arr, unsafe.Sizeof(&arr)) // &arr的类型 *[]int &arr占用的字节数是 8
err := json.Unmarshal([]byte(markArr), &arr)
if err != nil {
t.Log(err)
return
}
t.Log(arr)
var parr *[]int
t.Log(parr) // <nil>
t.Log(parr) // <nil>
t.Logf("&parr的类型 %T &parr占用的字节数是 %d", parr, unsafe.Sizeof(parr)) // parr的类型 *[]int parr占用的字节数是 8
err = json.Unmarshal([]byte(markArr), parr)
if err != nil {
t.Log(err) // json: Unmarshal(nil *[]int)
return
}
t.Log(parr)
}
go 的值传递。
结论
json unmarshal 时,注意要初始化数据,谨慎使用指针。
注
map, slice 初始化为 nil。