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

 

posted @ 2021-07-02 23:26  不吃陈皮  阅读(601)  评论(0编辑  收藏  举报