目录
1. 对interface的基本认识
遇到如下需求:将JSON,XML,YAML等格式的文本相互转换。
interface是Go有别于传统OOP编程的重要设计,这种非侵入式的接口设计,让接口实现和接口调用完美解耦。
对interface的第一印象如下:
- 非侵入式接口调用规范
- 定义一组函数调用规范,只要实现了约定的函数签名,即可实现接口赋值
- 是实现类似OOP中多态的重要工具
- 由于不需要显式声明继承关系,所以Go不再需要OOP的类型继承关系图
2. interface也是一种类型
从reflect的类型Kind定义可以看到,reflect.Interface也是一种数据类型。
也就是说Interface与int其实是可以同化处理的。
然而,interface与普通类型有所区别:
我们可以用已有重定义任何类型并为新类型增加method,然而重命名interface,却不能为其增加method。
type MyInt int
// It's ok to define method for normal type
func (m MyInt) Show() {
println(m)
}
type MyInterface interface{}
// invalid receiver type MyInterface (MyInterface is an interface type)
func (m MyInterface) Show() {
fmt.Println(m)
}
因此如果我们想通过如下方法来改变某个interface的JSON序列化方法是不可能实现的:
type MyInterface interface{}
// invalid receiver type *MyInterface (MyInterface is an interface type)
func (m *MyInterface) UnmarshalJSON(data []byte) error {
*m = append([]byte(nil), data...)
return nil
}
3. interface类型的序列化问题
我们时常会有如下需求:将JSON,XML,YAML等格式的文本相互转换。
与一般序列化需求已知输入文本的格式,只需要定义满足格式的struct等类型来unmarshal的需求不一样,
这里根本不关心具体的输入文本是什么格式,只需要有一个通用的Go数据结构,能够接收任意输入,并marshal为另一种格式的输出即可。
看起来interface{}正是我们想要的通用数据结构。
我们可以轻易实现如下通用序列化方法:
func TestUniversalUnmarshal(t *testing.T) {
var d struct {
D interface{}
}
var txt = `
{
"D":{
"a": 1,
"b": "foo"
}
}
`
json.Unmarshal([]byte(txt), &d)
fmt.Printf("unmarshal txt: %s\nGo data:%#v\n", txt, d)
// output:
// unmarshal txt:
// {
// "D":{
// "a": 1,
// "b": "foo"
// }
// }
// Go data:struct { D interface {} }{D:map[string]interface {}{"a":1, "b":"foo"}}
}
可以看到,通用数据结构D(interface{})完美的接收了任意json的输入,并转换为合法的Go通用数据结构map[string]interface{}。
因此,我们很容易想到通用的json unmarshal方法:
func FromJson(s string) (interface{}, error) {
var d struct {
D interface{}
}
ss := fmt.Sprintf(`{"D":%s}`, s)
err := json.Unmarshal([]byte(ss), &d)
return d.D, err
}
燃鹅,反序列化YAML让我们犯难了。我们不可能单纯的加2个大括号构造出一个合法的YAML字符串,因为YAML用空格缩进表示层级关系,意味着我们需要给每一行增加一个空格,才能构造出一个合法的YAML字符串。
反思以上方法,我们通过未初始化的interface{} D实现了接收任意类型的json输入,是否意味着我们可以用nil来调用json.Unmarshal达到相同的效果呢?
func TestUniversalUnmarshal2(t *testing.T) {
var d interface{}
var txt = `
{
"a": 1,
"b": "foo"
}
`
err := json.Unmarshal([]byte(txt), d)
if err != nil {
t.Fatal(err)
}
fmt.Printf("unmarshal txt: %s\nGo data:%#v, err=%v\n", txt, d, err)
// output:
// json: Unmarshal(nil)
}
可见,json.Unmarshal(data []byte, v interface{}) error不接受未初始化的interface{}(nil)作为输入参数。
通过简单的reflect测试,可以发现interface{}底层的秘密:
func TestInterfaceReflect(t *testing.T) {
var d interface{}
Unmarshal(nil, d)
Unmarshal(nil, &d)
// output:
// Unmarshal v=<nil> rt=<nil> rv=<invalid reflect.Value>
// Unmarshal v=(*interface {})(0xc00003a740) rt=*interface {} rv=0xc00003a740
// rt=ptr rv=0xc00003a740
// rtreal=interface rv=0xc00003a740
}
func Unmarshal(data []byte, v interface{}) error {
rv := reflect.ValueOf(v)
rt := reflect.TypeOf(v)
fmt.Printf("Unmarshal v=%#v rt=%v rv=%v\n", v, rt, rv)
if rt != nil {
fmt.Printf(" rt=%v rv=%v\n", rt.Kind(), rv)
if rt.Kind() == reflect.Ptr {
rtReal := rt.Elem()
fmt.Printf(" rtreal=%v rv=%v\n", rtReal.Kind(), rv)
}
}
return nil
}
通过interface{} -> interface{} 得到的是 nil type invalid value nil。
通过*interface{} -> interface{} 得到的是 ptr type -> interface{}。
因此,我们可以得知,interface{} 还可以接收 *interface 作为输入,得到的是ptr->interface的指针。这正是我们想要的通过interface{}接收任意文本输入作为通用Go数据结构的方法。
func FromJson(s string) (interface{}, error) {
var d interface{}
err := json.Unmarshal([]byte(s), &d) // decode to *interface{}
return d, err
}
func FromYaml(s string) (interface{}, error) {
var d interface{}
err := yaml.Unmarshal([]byte(s), &d) // decode to *interface{}
return d, err
}
踏破铁鞋无觅处,得来全不费工夫。这正是我们需要的接收任意合法字符串输入,不需要对输入数据做任何处理,即可得到与其等价的通用Go数据结构interface{}的方法。
基于此方法,我实现了通用的JSON,YAML,XML相互转换的库,在这里。
4. 结论
- Go interface是一种特殊的Go类型,不允许为该类型增加method
- interface{} 可以赋值为普通类型的Value,得到的数据是只读的
- interface{} 可赋值为普通类型的指针,得到的是reflect.Ptr类型,其Value是assignable的
- interface{} 接收为赋值的 interface{}作为输入,得到的还是未赋值的interface{}<nil>
- interface{} 可赋值为 *interface,得到的是reflect.Ptr类型,只值为interface类型,可用于反序列化为任意的Go数据结构