Go使用反射递归复制结构体
前言
go初学,今天本来有个需求涉及到用反射,然后花了时间边学边做,嗯,做完了之后发现做复杂了。缘由如下,有个结构体不能直接用,需要对里面的某个字段做一下调整,但是考虑到这个结构体里的其他字段会经常做一些变动,所以就想着使用反射自动化复制一份出来,并对需要调整的字段进行调整,以后再有增减字段,直接执行脚本就可以同步改动了。还是太天真,下面的例子是做了简化的例子,就瞎写,大家凑合看吧,我也不知道这个东西能有啥用~
代码结构
1. common里放的生成的结果。
2. scripts里的generate.go是实现,里面有一个main方法供脚本调用
3. scripts里的bash.sh是调用脚本。
4. struct.go里是要复制的结构体样本。
代码
struct.go
package reflectLearning type Student struct { Name string `json:"name"` Age int64 `json:"age"` Body *Body `json:"body"` Bodys []*Body `json:"bodys"` // 这里单纯是为了多样化 } type Body struct { Height int64 `json:"height"` Weight int64 `json:"weight"` }
generate.go
package main import ( "bufio" "fmt" "goLearning/reflectLearning" "os" "reflect" "strings" ) var ( voucherProtos = []reflect.Type{ reflect.TypeOf(reflectLearning.Student{}), } ) // 定义普通类型,非结构体 var baseTypes = map[reflect.Kind]bool{ reflect.Bool: true, reflect.Int: true, reflect.Int8: true, reflect.Int16: true, reflect.Int32: true, reflect.Int64: true, reflect.Uint: true, reflect.Uint8: true, reflect.Uint16: true, reflect.Uint32: true, reflect.Uint64: true, reflect.Uintptr: true, reflect.Float32: true, reflect.Float64: true, reflect.Map: true, reflect.String: true, } func main() { var voucherStructs = "package common\n" for _, proto := range voucherProtos { str := generateStructByType("", proto) voucherStructs += str } file, err := os.Create("../../common/request.go") if err != nil { fmt.Println("文件打开失败", err) } //及时关闭file句柄 defer file.Close() //写入文件时,使用带缓存的 *Writer write := bufio.NewWriter(file) write.WriteString(voucherStructs) //Flush将缓存的文件真正写入到文件中 write.Flush() } // 递归生成结构体方法 func generateStructByType(content string, tp reflect.Type) string { str := fmt.Sprintf("type %s struct {\n", tp.Name()) if i := strings.IndexAny(content, str); i != -1 { // 避免重复生成结构体 return "" } var voucherStructs string // 遍历结构体的所有字段 for i := 0; i < tp.NumField(); i++ { field := tp.Field(i) if baseTypes[field.Type.Kind()] { // 基础类型 fieldType := field.Type.Name() if field.Name == "VoucherID" { fieldType = "string" } str += fmt.Sprintf("%s %s `%s`\n", field.Name, fieldType, field.Tag) } else if field.Type.Kind() == reflect.Ptr { // 指针类型 pointType := field.Type.Elem() // 指针指向的类型 if !baseTypes[pointType.Kind()] { // 不是基础类型则认为是结构体,递归生成结构体 voucherStructs += generateStructByType(voucherStructs, pointType) } str += fmt.Sprintf("%s *%s `%s`\n", field.Name, pointType.Name(), field.Tag) } else if field.Type.Kind() == reflect.Slice { // 数组,分为基本类型数组,结构体数组,指针基本类型数据,指针结构体数组 fieldType := "[]" ele := field.Type.Elem() // 先判断是不是指针数组 if ele.Kind() == reflect.Ptr { fieldType += "*" // 指针类型是基本类型还是结构体 subEle := ele.Elem() if baseTypes[subEle.Kind()] { // 基本类型 fieldType += subEle.Name() // 结构体 } else { fieldType += subEle.Name() voucherStructs += generateStructByType(voucherStructs, subEle) } str += fmt.Sprintf("%s %s `%s`\n", field.Name, fieldType, field.Tag) } } } str += "}\n" voucherStructs = voucherStructs + str return voucherStructs }
bash.sh
#!/C:/Program Files/Git/bin/bash.exe # 第一行是在win系统下才需要指定,mac系统不需要 go run -mod=vendor generate.go cd ../../common # 生成完代码后整理一下格式,不然不好看 gofmt -w ./
成品
request.go
package common type Body struct { Height int64 `json:"height"` Weight int64 `json:"weight"` } type Student struct { Name string `json:"name"` Age *int64 `json:"age"` Body *Body `json:"body"` Bodys []*Body `json:"bodys"` }
结语
改动Student结构后,点击执行bash.sh就能同步到request.go里去了。一天天的,不知道在干啥~