Go使用反射递归复制结构体

前言

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里去了。一天天的,不知道在干啥~

Go使用反射递归复制结构体

 

 

  

  

 

 

Go使用反射递归复制结构体

上一篇:C# 值类型


下一篇:leetcode-存在重复元素