encode/json 的Marshal函数对于指针和对象返回得到结果不一样

package main

import (
    "encoding/json"
    "fmt"
)

type X struct {
    B json.RawMessage `json:"b"`
}

func main() {
    x := X{B: []byte(`{"test":"t"}`)}

    b, err := json.Marshal(x)
    if err != nil {
        fmt.Println(err.Error())
        return
    }
    fmt.Printf("%s\n", b)

    b, err = json.Marshal(&x)
    if err != nil {
        fmt.Println(err.Error())
        return
    }
    fmt.Printf("%s\n", b)

    return
}

代码如上,结果为 {"b":"eyJ0ZXN0IjoidCJ9"} {"b":{"test":"t"}} 为什么会不同呢

已邀请:

stevewang

赞同来自: 九命猫 niugou

这个问题实际上取决于T.B这个值是否可以取地址,即func (reflect.Value) CanAddr() bool的返回值。

规则是:如果T.B的值可以取址,因为RawMessage实现了json.Marshaler接口,因此调用RawMessage.Marshal函数,该函数的实现是直接返回原生[]byte;如果T.B不能取址,则当作原生[]byte处理,编码成base64字符串。

那么问题又来了:什么时候可以取址,什么时候不能取址呢?

1.struct中的成员类型如果是*[]byte并且值非空,那么总是可以取址的。

2.struct中的成员类型如果是[]byte且值非空,那么是否可以取址则取决于struct对象是否可以取址。在原文程序中,第一种情况中传给json.Marshal函数的struct对象拷贝是不能取址的,第二种情况中函数参数是struct指针,因此是可以取址的。

结论: 如果希望输出结果是{"b":{"test":"t"}},那么要么调用json.Marshal(&x),或者把X.B的类型改为指针类型*json.RawMessage

注:是否可以取址可以用以下实验程序验证:

package main

import (
    "reflect"
    "fmt"
)

type T1 struct {
    B []byte
}

type T2 struct {
    B *[]byte
}
func Check(i interface{}) {
    v := reflect.ValueOf(i)
    t := reflect.TypeOf(i)
    if v.Kind() == reflect.Ptr {
        v = v.Elem()
        t = t.Elem()
    }
    count := t.NumField()
    for i := 0; i < count; i++ {
        fv := v.Field(i)
        ft := t.Field(i).Type
        if fv.Kind() == reflect.Ptr {
            fv = fv.Elem()
            ft = ft.Elem()
        }
        fmt.Printf("[%d]type=%v CanAddr=%v\n", i, ft, fv.CanAddr())
    }
}

func main() {
    b := []byte{1, 2, 3}
    var t1 = T1{
        B: b,
    }
    var t2 = T2{
        B: &b,
    }
    Check(t1)
    Check(&t1)  
    Check(t2)
    Check(&t2)  
}

输出:

[0]type=[]uint8 CanAddr=false
[0]type=[]uint8 CanAddr=true
[0]type=[]uint8 CanAddr=true
[0]type=[]uint8 CanAddr=true

stevewang

赞同来自: 九命猫 niugou

补充说明一下:上面我说到X.B要能取址才能执行RawMessage.Marshal,是因为是*json.RawMessage而不是json.RawMessage实现了json.Marshaler接口。 因此,我们如果自己实现json.Marshaler接口,就没有取址的限制了。

package main

import (
    "encoding/json"
    "fmt"
)

type RawMessage []byte

func (this RawMessage) MarshalJSON() ([]byte, error) {
    return []byte(this), nil
}

type X struct {
    B RawMessage `json:"b"`
}

func main() {
    x := X{B: []byte(`{"test":"t"}`)}
    b, err := json.Marshal(x)
    if err != nil {
        fmt.Println(err.Error())
        return
    }
    fmt.Printf("%s\n", b)
}

输出:

{"b":{"test":"t"}}

要回复问题请先登录注册