根据将对象转换为指针(有时是指针,有时是映射)的时间来改变编组功能

时间:2019-01-14 04:27:22

标签: json pointers go marshalling unmarshalling

我正在实现一些用于序列化对象的方法(标准库中可能已经存在,但是我想获得经验)。 我能够同时获取json.Marshal和json.Unmarshal来将我的数据正确转换为字符串,然后读回一个对象,但是我注意到一些我想更好理解的有趣行为。

为了测试json包的功能,我创建了一个测试结构:

type Test struct{
    T int
    S string
    F float32
}

运行

o := Test{32, "struct", 12.07}
b, e := json.Marshal(o)
fmt.Println(string(b), e)

打印

{"T":32,"S":"struct","F":12.07} <nil>

这是我所期望的。但是,根据将对象转换为指针的时间,解组时可以获得两种不同的结果:

// Test 1
var o2 interface{} = &o
e = json.Unmarshal(b, o2)
fmt.Println(o2, e)

打印

// Output 1
&{32 struct 12.07} <nil>

同时将o2定义为值而不是指针,然后使用&o2, ie。

调用Unmarshal
// Test 2
var o2 interface{} = o
e = json.Unmarshal(b, &o2)
fmt.Println(o2, e)

打印

// Output 2
map[T:32 S:struct F:12.07] <nil>

足够有趣的是,全部四个

// Test 3
var o2 Test = o
e = json.Unmarshal(b, &o2)
fmt.Println(o2, e)

// Test 4
var o2 *Test = &o
e = json.Unmarshal(b, &o2)
fmt.Println(*o2, e)

// Test 5
var o2 *Test = &o
e = json.Unmarshal(b, o2)
fmt.Println(o2, e)

// Test 6
var o2 *Test = &o
e = json.Unmarshal(b, &o2)
fmt.Println(o2, e)

打印以下内容之一:

// Output 3, 4
{32 struct 12.07} <nil>

// Output 5, 6
&{32 struct 12.07} <nil>

测试1和2使我相信,在将某些东西分配给“超类”时,某些运行时信息会丢失...看来,将指向我的struct的指针分配给o2会给Unmarshal函数提供有关它的更多类型数据应该比将我的结构分配给o2然后将指向o2的指针传递给Unmarshal进行读取。这有点道理;在编译时,测试1将接口{}传递给Unmarshal,而测试2将指针传递给接口{}。即使如此,我也会认为该参数的运行时类型是相同的(* Test)。有人可以解释为什么Go这样工作吗?

支持我的结论的事实是,将o2声明为Test或* Test允许Unmarshal(因此将* Test作为其参数)始终将我的数据读取为结构而不是映射。

奇怪的是,测试4和测试6表明将指针传递给指向我的结构的指针是完全可以接受的,并且正确解组(?)可以设置结构的值。我原本希望这些测试会出现运行时错误,因为Unmarshal应该试图取消对双指针的引用,并将结果指针设置为其正在读取的序列化结构。自动多指针取消引用可能只是此功能的功能,但官方文档中并未提及。我不太担心后面的这些测试。我主要只是对导致测试1和测试2发生差异的原因感兴趣。

**编辑于2018年1月14日,将第二个json.marshal更改为json.unmarshal,从复制/粘贴的代码中删除不正确的括号

1 个答案:

答案 0 :(得分:2)

解组函数将遍历指针和包含指针值的接口以找到目标值。接口中的非指针值将被忽略,因为这些值不是addressable。 (此描述省略了对该问题不重要的细节。)

如果目标是interface{},而JSON是对象,则JSON将被解编为map[string]interface{},并将该值存储在接口中。 (这在json.Unmarshal documentation中有描述)。

测试1:解组的参数为*Test。解组函数将遍历指针并解码为Test结构。

测试2:解组的参数是指向包含interface{}的{​​{1}}的指针。解组函数遍历指针以获得Testinterface{}中的Test值将被忽略,因为它不是addressable。因为目的地是interface{},而JSON是对象,所以JSON被解码为interface{}

3、4、5和6中的代码无法编译。我将假设删除map[string]interface{}之后的{}

Test

测试3和5:解组的参数是var o2 Test = o // 3 var o2 *Test = &o // 4 var o2 *Test = &o // 5 var o2 *Test = &o // 6 。与#1相同。

测试4和6:参数为*Test。解组函数将遍历指针并解码为**Test结构。

因为Go没有像“超类”那样的东西,所以这不是问题。