基于对象何时转换为指针(有时是指针,有时是映射)更改解组功能
Change in unmarshalling functionality based on when object is converted to pointer (sometimes pointer, sometimes map)
我正在实现一些序列化对象的方法(标准库中可能已经存在,但我想体验一下)。
我能够让 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 调用 Unmarshal,ie.
// 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 让我相信,当将某些东西分配给 "superclass" 时,一些运行时信息会丢失...似乎将指向我的结构的指针分配给 o2 会为 Unmarshal 函数提供更多类型与将我的结构分配给 o2 然后将指向 o2 的指针传递给 Unmarshal 相比,它应该读取的数据。这有点道理;在编译时,测试 1 将 interface{} 传递给 Unmarshal,而 Test 2 将指针传递给 interface{}。即便如此,我还是认为参数的运行时类型会是相同的(*测试)。有人可以解释为什么 Go 会这样工作吗?
支持我的结论的事实是,将 o2 声明为 Test 或 *Test 允许 Unmarshal(因此接收 *Test 作为其参数)始终将我的数据读取为结构而不是映射。
奇怪的是,测试 4 和测试 6 表明将指针传递给指向我的结构的指针是完全可以接受的,并且 Unmarshal 正确地(?)设置了结构的值。我本以为这些测试会出现运行时错误,因为 Unmarshal 应该尝试取消引用双指针并将结果指针设置为它正在读取的序列化结构。 Automatic multi-pointer dereferencing 可能只是这个功能的一个特性,但在官方文档中并没有提到。我不太关心后面的这些测试。我主要只对导致测试 1 和测试 2 之间的差异的原因感兴趣。
** 2018 年 1 月 14 日编辑,将第二个 json.Marshal 更改为 json.Unmarshal,从 copy/pasted 代码
中删除不正确的括号
解组函数遍历指针和包含指针值的接口以找到目标值。接口中的非指针值将被忽略,因为这些值不是 addressable。 (此描述省略了对问题不重要的细节)。
如果目标是一个 interface{}
并且 JSON 是一个对象,那么 JSON 被解组为 map[string]interface{}
并且该值存储在接口中. (这在 json.Unmarshal documentation 中有描述)。
测试 1:解组的参数是 *Test
。 unmarshal 函数遍历指针并解码为 Test
结构。
测试 2:unmarshal 的参数是指向包含 Test
的 interface{}
的指针。 unmarshal 函数遍历指针以获得 interface{}
。 interface{}
中的 Test
值被忽略,因为它不是 addressable。因为目标是一个 interface{}
而 JSON 是一个对象,所以 JSON 被解码为 map[string]interface{}
。
3、4、5、6中的代码编译不通过。我将继续假设删除 Test
之后的 {}
。
var o2 Test = o // 3
var o2 *Test = &o // 4
var o2 *Test = &o // 5
var o2 *Test = &o // 6
测试 3 和 5:unmarshal 的参数是 *Test
。这与#1 相同。
测试 4 和 6:参数是 **Test
。 unmarshal 函数遍历指针并解码到 Test
结构。
因为 Go 没有任何类似 "superclass" 的东西,所以这不是问题所在。
我正在实现一些序列化对象的方法(标准库中可能已经存在,但我想体验一下)。 我能够让 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 调用 Unmarshal,ie.
// 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 让我相信,当将某些东西分配给 "superclass" 时,一些运行时信息会丢失...似乎将指向我的结构的指针分配给 o2 会为 Unmarshal 函数提供更多类型与将我的结构分配给 o2 然后将指向 o2 的指针传递给 Unmarshal 相比,它应该读取的数据。这有点道理;在编译时,测试 1 将 interface{} 传递给 Unmarshal,而 Test 2 将指针传递给 interface{}。即便如此,我还是认为参数的运行时类型会是相同的(*测试)。有人可以解释为什么 Go 会这样工作吗?
支持我的结论的事实是,将 o2 声明为 Test 或 *Test 允许 Unmarshal(因此接收 *Test 作为其参数)始终将我的数据读取为结构而不是映射。
奇怪的是,测试 4 和测试 6 表明将指针传递给指向我的结构的指针是完全可以接受的,并且 Unmarshal 正确地(?)设置了结构的值。我本以为这些测试会出现运行时错误,因为 Unmarshal 应该尝试取消引用双指针并将结果指针设置为它正在读取的序列化结构。 Automatic multi-pointer dereferencing 可能只是这个功能的一个特性,但在官方文档中并没有提到。我不太关心后面的这些测试。我主要只对导致测试 1 和测试 2 之间的差异的原因感兴趣。
** 2018 年 1 月 14 日编辑,将第二个 json.Marshal 更改为 json.Unmarshal,从 copy/pasted 代码
中删除不正确的括号解组函数遍历指针和包含指针值的接口以找到目标值。接口中的非指针值将被忽略,因为这些值不是 addressable。 (此描述省略了对问题不重要的细节)。
如果目标是一个 interface{}
并且 JSON 是一个对象,那么 JSON 被解组为 map[string]interface{}
并且该值存储在接口中. (这在 json.Unmarshal documentation 中有描述)。
测试 1:解组的参数是 *Test
。 unmarshal 函数遍历指针并解码为 Test
结构。
测试 2:unmarshal 的参数是指向包含 Test
的 interface{}
的指针。 unmarshal 函数遍历指针以获得 interface{}
。 interface{}
中的 Test
值被忽略,因为它不是 addressable。因为目标是一个 interface{}
而 JSON 是一个对象,所以 JSON 被解码为 map[string]interface{}
。
3、4、5、6中的代码编译不通过。我将继续假设删除 Test
之后的 {}
。
var o2 Test = o // 3
var o2 *Test = &o // 4
var o2 *Test = &o // 5
var o2 *Test = &o // 6
测试 3 和 5:unmarshal 的参数是 *Test
。这与#1 相同。
测试 4 和 6:参数是 **Test
。 unmarshal 函数遍历指针并解码到 Test
结构。
因为 Go 没有任何类似 "superclass" 的东西,所以这不是问题所在。