今天尝试了一下使用go语言中的反射来将struct类型转换成xml,结果相当纠结。首先去看了一下go的reflect包的实现,根据go的规则,首先应该去看一个NewXXX的方法,结果发现了一个叫NewValue的方法,通过这个方法我们能够得到一个Value接口。另外我们还应该注意到,go的反映实现中将Type和Value分开了,于是还有另外一个接口Type.
type Value interface { // Type returns the value's type. Type() Type // Interface returns the value as an interface{}. Interface() interface{} // CanSet returns whether the value can be changed. // Values obtained by the use of non-exported struct fields // can be used in Get but not Set. // If CanSet() returns false, calling the type-specific Set // will cause a crash. CanSet() bool // SetValue assigns v to the value; v must have the same type as the value. SetValue(v Value) // Addr returns a pointer to the underlying data. // It is for advanced clients that also // import the "unsafe" package. Addr() uintptr // Method returns a FuncValue corresponding to the value's i'th method. // The arguments to a Call on the returned FuncValue // should not include a receiver; the FuncValue will use // the value as the receiver. Method(i int) *FuncValue // contains unexported methods } type Type interface { // PkgPath returns the type's package path. // The package path is a full package import path like "container/vector". // PkgPath returns an empty string for unnamed types. PkgPath() string // Name returns the type's name within its package. // Name returns an empty string for unnamed types. Name() string // String returns a string representation of the type. // The string representation may use shortened package names // (e.g., vector instead of "container/vector") and is not // guaranteed to be unique among types. To test for equality, // compare the Types directly. String() string // Size returns the number of bytes needed to store // a value of the given type; it is analogous to unsafe.Sizeof. Size() uintptr // Bits returns the size of the type in bits. // It is intended for use with numeric types and may overflow // when used for composite types. Bits() int // Align returns the alignment of a value of this type // when allocated in memory. Align() int // FieldAlign returns the alignment of a value of this type // when used as a field in a struct. FieldAlign() int // Kind returns the specific kind of this type. Kind() Kind // For non-interface types, Method returns the i'th method with receiver T. // For interface types, Method returns the i'th method in the interface. // NumMethod returns the number of such methods. Method(int) Method NumMethod() int // contains unexported methods }
然后包里还根据每种数据类型都定义了相应的XXXType和XXXValue结构体,那么我们如何得到这些结构体的实例呢?因为只有得到这些构体的实例才能利用反映来操纵这些数据。于是很丑陋的地方来了,你需要使用.(type)来得到当前这个变量的类型,而这个使用必须在switch中,请看下面的例子
func test(data interface{}) { switch rvalue := reflect.NewValue(data).(type) { default: println(reflect.Typeof(rvalue).String()) } switch rtype := reflect.Typeof(data).(type) { default: println(reflect.Typeof(rtype).String()) } switch rvalue := data.(type) { default: println(reflect.Typeof(rvalue).String()) } }
NewValue创建了一个data的Value接口,但是这个接口是谁实现的呢?通过.(type)就可以得到。
Typeof创建了一个data的Type接口,同样的这个接口也可以通过.(type)得到它的实现。
而如果不使用reflect里的任何方法直接对变量data使用.(type),那么就会得到这个接口的实现。
因此从这里我们可以看到go语言中使用反映需要两个条件,一个是通过type得到它的实现变量,一个是通过reflect里提供的接口来进行操作。
但是很恶心的是,每次操作都必须通过switch case来执行,比如最后我的代码就变成了如下这个样子:
func asString(val interface{}) string { switch value := reflect.NewValue(val).(type) { case *reflect.StringValue: return value.Get() case *reflect.BoolValue: return strconv.Btoa(value.Get()) case *reflect.IntValue: return strconv.Itoa64(value.Get()) default: panic("invalid type for xml generator") } return "" } func toXml(val interface{}, writer io.Writer) { switch value := reflect.NewValue(val).(type) { case *reflect.StructValue: writer.Write([]byte("<")) writer.Write([]byte(reflect.Typeof(val).Name())) switch vtype := reflect.NewValue(val).Type().(type) { case *reflect.StructType: //compose attribute for i := 0; i < value.NumField(); i++ { field := vtype.Field(i) if field.Tag != "attr" { continue } writer.Write([]byte(" ")) writer.Write([]byte(field.Name)) writer.Write([]byte("=/"")) writer.Write([]byte(asString(value.Field(i).Interface()))) writer.Write([]byte("/"")) } writer.Write([]byte(">")) //compose children for i := 0; i < value.NumField(); i++ { field := vtype.Field(i) if field.Tag == "attr" { continue } switch child := value.Field(i).(type) { case *reflect.StructValue: toXml(child.Interface(), writer) case *reflect.ArrayValue: case *reflect.SliceValue: writer.Write([]byte("<")) writer.Write([]byte(field.Name)) writer.Write([]byte(">")) for i := 0; i < child.Len(); i++ { switch elem := child.Elem(i).(type) { case *reflect.StructValue: toXml(elem.Interface(), writer) default: panic("invalid type for array, only struct supported") } } writer.Write([]byte("</")) writer.Write([]byte(field.Name)) writer.Write([]byte(">")) default: writer.Write([]byte(asString(child.Interface()))) } } //compose end writer.Write([]byte("</")) writer.Write([]byte(reflect.Typeof(val).Name())) writer.Write([]byte(">")) default: panic("invalid type: only array, slice, struct and primitive type supported") } default: panic("invalid type, only struct supported") } }