我正在实现一些用于序列化对象的方法(标准库中可能已经存在,但是我想获得经验)。 我能够同时获取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,从复制/粘贴的代码中删除不正确的括号
答案 0 :(得分:2)
解组函数将遍历指针和包含指针值的接口以找到目标值。接口中的非指针值将被忽略,因为这些值不是addressable。 (此描述省略了对该问题不重要的细节。)
如果目标是interface{}
,而JSON是对象,则JSON将被解编为map[string]interface{}
,并将该值存储在接口中。 (这在json.Unmarshal documentation中有描述)。
测试1:解组的参数为*Test
。解组函数将遍历指针并解码为Test
结构。
测试2:解组的参数是指向包含interface{}
的{{1}}的指针。解组函数遍历指针以获得Test
。 interface{}
中的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没有像“超类”那样的东西,所以这不是问题。