在 go 语言中利用反射精简代码

Golang 最精简工程结构 1.3.1 最简洁工程结构 最小工程,也就是一个用golang语言开发的项目所使用的最精简的目录结构。在这个最精简的目录结构中,可以进行完整的项目开发过程。 创建项目目录 创建workspace目录,接着在workspace目录下边创建3个目录,分别是src、bin、pkg。 bin和pkg也可以不创建,在编译项目时,会自动生成这两个目录。 workspace ------bin ----... 阅读详情

反射是 Go 语言中非常重要的一个知识点。反射是设计优雅程序的法宝,orm json 序列化,参数校验都离不开它,我们今天以一个业务开发中的实例,来简单讲解下反射在日常开发中的用处。

本文使用的 case 皆为项目开发中的实例,为了脱敏简化了代码

相信大家在使用 go 编写业务代码的时候都会写过这样的代码,这类代码的本质是,将一个集合中的数据按照名称绑定到结构体的对应属性上去,集合的类型不限于 map slice struct 甚至可以是 interface[^interface],

[^interface]: 这个就比较 trick 了

type TestValue struct {
        IntValue int
        StringValue string
        IntArray   []int
        StringArray []string
    }

    dataMap := map[string]string{
        "int_value":"1",
        "string_value":"str",
        "int_array":"[1,2,3]",
        "string_array":"[\"1\",\"2\",\"3\"]",
    }

    config := TestValue{}

    if value, ok := dataMap["int_value"]; ok {
        config.IntValue, _ = datautil.TransToInt64(value)
    }
    if value, ok := dataMap["string_value"]; ok {
        config.StringValue = value
    }
    if value, ok := dataMap["int_array"]; ok {
        config.IntArray = stringToIntArray(value)
    }
    if value, ok := dataMap["string_array"]; ok {
        config.StringArray = stringToStrArray(value)
    }

    return config

这部分代码中最挫的地方就是结构体赋值的时候一个一个进行的复制,若整个结构体非常大,赋值的代码可能会写满满一屏,bug出现的几率也就大大增加,我们的目的就是通过反射来简化赋值的步骤,通过一个方法将集合中的数据绑定到结构体上


v2-a16814d4b3c2ea47cec720c20b1a48b7_b.jpg


反射简述

要做到这一步,我们首先了解下,在 go 语言中,我们的变量是由什么组成的

  • _type 类型信息
  • *data 指向实际值的指针
  • itab 接口方法

图上第一个 type 是一个反射类型对象,表示了变量类型的一些信息,第二个表示结构体属性对应的的 type,包含了结构体属性的一些信息

reflect.Type : /go/src/reflect/value.go:36


v2-5eb7ceb05a95635ae9e9c4cddf561633_b.jpg


bind function

看到这张图我们大概就明白应该怎样做了,目标是编写一个绑定方法,必须建立一个绑定关系,把这个结构体加上一个 tag ,通过 tag 和 map 中的数据建立关联

// 创建一个具有深刻含义的 tag
    type TestValue struct {
        IntValue     int            `qiudianzan:"int_value"`        
        StringValue string        `qiudianzan:"string_value"`
        IntArray       []int        `qiudianzan:"int_array"`
        StringArray []string    `qiudianzan:"string_array"`
    }

紧接着获取结构体的 tag ,通过反射轻而易举的做到了

func bind(configMap map[string]string, result interface{}) error {

    v := reflect.ValueOf(result).Elem()
    t := v.Type()

    for i := 0; i < t.NumField(); i++ {
        tag := t.Field(i).Tag.Get("qiudianzan")
        fmt.Println(tag)
    }

绑定关系完成以后,就需要向结构体写入 map 中的值,这时候问题来了,map 中的数据结构都是 string ,但是结构体属性类型五花八门,并不能直接将 map 中的数据写入,还需要根据结构体属性类型做一步类型转换

v.Field(i).SetInt(res)
    v.Field(i).SetUint(res)
    v.Field(i).SetFloat(res)
    .
    .
    .

通过反射可以获取属性的两种表示类型的反射对象

reflect.Type    // 静态类型
    reflect.Kind    // 底层数据的类型

我们通过下面的例子来确定使用哪一个

type A struct {
    }

    func main() {
        var a A
        kinda := reflect.ValueOf(a).Kind()
        typea := reflect.TypeOf(a)

        fmt.Println(kinda)
        fmt.Println(typea)
    }
struct
    main.A

变量 a 的静态类型为 A,但是 a 的底层数据类型则为 struct,所以我们想根据类型解析,这里说的类型是指的 reflect.Kind

通过底层数据类型来转换 map 中的数据

switch v.Field(i).Kind() {

        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
            res, err := strconv.ParseInt(value, 10, 64)
            if err != nil {
                return err
            }
            v.Field(i).SetInt(res)

        case reflect.String:
            v.Field(i).SetString(value)

        case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
            res, err := strconv.ParseUint(value, 10, 64)
            if err != nil {
                return err
            }
            v.Field(i).SetUint(res)

再稍稍整理下添加一点细节,一个简单的绑定函数就大功告成了

func bind(configMap map[string]string, result interface{}) error {

    // 被绑定的结构体非指针错误返回
    if reflect.ValueOf(result).Kind() != reflect.Ptr {
        return errors.New("input not point")
    }

    // 被绑定的结构体指针为 null 错误返回
    if reflect.ValueOf(result).IsNil() {
        return errors.New("input is null")
    }

    v := reflect.ValueOf(result).Elem()
    t := v.Type()

    for i := 0; i < t.NumField(); i++ {
        tag := t.Field(i).Tag.Get("json")

        // map 中没该变量有则跳过
        value, ok := configMap[tag]
        if !ok {
            continue
        }

        // 跳过结构体中不可 set 的私有变量
        if !v.Field(i).CanSet() {
            continue
        }

        switch v.Field(i).Kind() {
        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
            res, err := strconv.ParseInt(value, 10, 64)
            if err != nil {
                return err
            }
            v.Field(i).SetInt(res)
        case reflect.String:
            v.Field(i).SetString(value)
        case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
            res, err := strconv.ParseUint(value, 10, 64)
            if err != nil {
                return err
            }
            v.Field(i).SetUint(res)
        case reflect.Float32:
            res, err := strconv.ParseFloat(value, 32)
            if err != nil {
                return err
            }
            v.Field(i).SetFloat(res)
        case reflect.Float64:
            res, err := strconv.ParseFloat(value, 64)
            if err != nil {
                return err
            }
            v.Field(i).SetFloat(res)
        case reflect.Slice:
            var strArray []string
            var valArray []reflect.Value
            var valArr reflect.Value
            elemKind := t.Field(i).Type.Elem().Kind()
            elemType := t.Field(i).Type.Elem()

            value = strings.Trim(strings.Trim(value, "["), "]")
            strArray = strings.Split(value, ",")

            switch elemKind {
            case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
                for _, e := range strArray {
                    ee, err := strconv.ParseInt(e, 10, 64)
                    if err != nil {
                        return err
                    }
                    valArray = append(valArray, reflect.ValueOf(ee).Convert(elemType))
                }
            case reflect.String:
                for _, e := range strArray {
                    valArray = append(valArray, reflect.ValueOf(strings.Trim(e, "\"")).Convert(elemType))
                }
            }

            valArr = reflect.Append(v.Field(i), valArray...)
            v.Field(i).Set(valArr)
        }
    }

    return nil
}

之前的看起来非常难看的代码瞬间就变得很简单

type TestValue struct {
        IntValue int
        StringValue string
        IntArray   []int
        StringArray []string
    }

    dataMap := map[string]string{
        "int_value":"1",
        "string_value":"str",
        "int_array":"[1,2,3]",
        "string_array":"[\"1\",\"2\",\"3\"]",
    }

    config := TestValue{}

    err := bind(dataMap,&config)

在这里只是提供一种绑定的思路,其实在实际开发中,遇到结构体值绑定/校验/格式化/方法绑定,都可以使用类似的思路,避免 one by one 的编写代码

这是使用这种思路的一些开源工具

  1. 结构体参数校验 gopkg.in/go-playground/
  2. 格式化 json.Unmarshal
  3. orm工具 github.com/jmoiron/sqlx 将结构体属性转换成 pre sql

作者:许睿


滴滴云-为开发者而生 滴滴云使者

【Golang】Go语言中的反射原理解析与应用实战 反射(Reflection)是计算机科学中的一个重要概念,它允许程序在运行时检查变量和值,获取它们的类型信息,并且能够修改它们。 Go语言通过内置的reflect包提供了反射功能,使得开发者可以编写灵活的代码,处理各种不同类型的值,而不必在编译时就知道这些值的具体类型。 本文将结合实际案例,详细介绍Go语言中反射的基本概念、关键函数以及使用场景。 阅读详情

相关推荐

Go基础:一文理解Go语言的反射处理

文章摘要 本文深入探讨了Go语言中的反射机制及其应用场景。反射通过reflect包实现,核心是reflect.Type和reflect.Value两个类型,分别用于获取类型信息和操作值。反射适用于序列化、框架开发等场景,但需注意避免在常规业务逻辑中使用。文章详细解析了反射三大定律:1)接口值到反射对象的转换;2)反射对象回接口值的逆向过程;3)修改反射对象必须确保其值可设置。最后通过案例代码展示了如何利用反射检查任意值的类型、字段和元素。合理使用反射能增强代码灵活性,但需权衡性能开销。

数据知道的博客 6360

利用reflect设置struct的字段

参考golang反射三大规则 reflect.Value区分CanSet和Can not Set的, 所以, 必须要返回成Can set的reflect.Value 如: s := reflect.ValueOf(&t).Elem() 然后就可以happy的设值了, 可是不能随便设值的, 一个通用的方法就是使用Set(v Value)方法, 所以之前必须将值转成reflect.V

三少GG 7535

STM32 FOC电机控制:手把手教你用CubeMX配置TIM1中心对齐PWM(含代码避坑)

本文详细解析了STM32 FOC电机控制中TIM1中心对齐PWM的配置方法,涵盖CubeMX图形化设置、HAL库代码调用及常见问题排查。特别针对L6230Q驱动芯片的使能逻辑和死区时间配置提供实用指南,帮助开发者高效实现无刷电机的高性能控制。

weixin_27027459的博客 443

Go语言最简化的快速排序代码 - 修正

#Go语言入门 学习Go时,发现Go可以将代码写得极其简单,下面是我实现的一个快速排序代码: //Start from here: package qsort func quickSort(values []int, left, right int) { i, j := left, right for i != j { for values[j] >= values[left] &&...

Cloud_View的博客 448

嘉立创封装图下载后必做的3步优化(附批量删除机械层技巧)

本文针对从嘉立创下载的封装图,系统讲解了三步核心优化流程,以解决其直接使用时的常见问题。重点介绍了如何高效批量删除冗余的机械层,避免制造错误;并详细说明了规范丝印层间距、统一焊盘命名等关键步骤,帮助工程师将第三方封装转化为可靠、合规的设计资源,从而提升PCB设计效率与文件质量。

weixin_29227585的博客 433

CODESYS安装全攻略:从下载到配置的保姆级教程(含常见问题解决)

本文提供了一份详细的CODESYS安装与配置指南,涵盖从系统环境准备、安装包下载、Development System安装到Runtime System配置的全过程。教程重点解决了安装过程中的常见权限、兼容性和连接问题,并指导用户完成第一个PLC程序的创建与调试,帮助工业自动化开发者快速上手这一通用开发平台。

kkk56的博客 421

Electron 安全必知完整指南:上下文隔离、沙箱与 CSP 的 10 个关键实践

Electron 让你用 JavaScript、HTML 和 CSS 构建跨平台桌面应用,但桌面应用里的 JavaScript 权限远超普通网页——它能读写文件、执行系统命令。权限越大,风险越大。本文结合 Electron 官方安全文档 [docs/tutorial/security.md](https://link.gitcode.com/i/9157eecca8c3bc2ea89f7bfdb5

gitblog_00498的博客 884

Ubuntu中交叉编译armadillo库

网上关于交叉编译armdillo库比较少,借鉴了一些但是在前几天编译时总是磕磕绊绊,于是写一个详细的编译过程。交叉编译armdillo库包含两个步骤:交叉编译依赖库和交叉编译armdillo库。

weixin_39813867的博客 1879

Go JSON 序列化性能优化

明确使用具体结构体类型,或通过代码生成工具(如`easyjson`)提前生成解析代码,能彻底规避反射。例如,将`map[string]interface{}`替换为预定义的`struct`,性能可提升数倍。标准库的通用性牺牲了部分性能,而第三方库如`json-iterator/go`和`ffjson`通过代码生成或优化反射逻辑,速度可提升2-5倍。默认情况下,`encoding/json`通过反射获取字段名,但反射开销较大。例如,使用`json:"name"`替代默认的字段名解析,能小幅提升性能。

cxkseq_484的博客 316

基于DSP28335的Matlab/Simulink代码生成实验七:A/D采样

实验七:讲解如何使用DSP的ADC模块,通过代码生成技术控制DSP进行电流采样,是后续实现电机闭环控制的重要环节。通过简单的实验来熟悉代码生成的过程。最终实现复杂算法代码自动生成,进行高性能电机控制!

m0_73869369的博客 4725

手把手教你配置SAP EWM HU类型及号段分配(含常见错误排查)

本文详细介绍了SAP EWM系统中HU类型及号段分配的配置方法,包括环境准备、包材主数据配置、存储类型关联及号段管理。通过实战案例和常见错误排查,帮助用户高效完成HU配置,提升仓储管理效率。重点讲解了EWM后台配置和号段配置的关键步骤与技巧。

weixin_33670786的博客 443

一起学Go吧! Go语言反射篇

文章目录前言什么是反射?GoLang中反射的基本概念reflect 包反射的类型对象(reflect.Type)反射的类型(Type)与种类(Kind)指针与指针指向的元素使用反射获取结构体的成员类型结构体标签(Struct Tag)后记 前言 最近快到清明节了,大家可以在学习之余也适当放松放松,回老家扫墓啥的。同时可以利用清明节的假期充实自己的专业技能(英雄联盟LOL???? )或者是复习学习过的内容!???? 什么是反射? 反射(reflection)是在 Java 出现后迅速流行起来的一种概念,通

jmu201821122110 2891

家用智能监控摄像机如何使用NAS进行存储/备份(小米智能摄像机如何解决群晖 / 飞牛 NAS 识别失败、文件目录不显示问题)

小米摄像头NAS存储总出问题?🤯 别慌!本文揭秘NAS底层逻辑,群晖、飞牛实战指南。手把手教你排查解决所有难题,轻松搞定监控录像安全备份!🔒

XiaoqiangClub的博客 2697

盘点一下Go语言的反射用法

反射可以认为是程序在运行时的一种能力,反射可以在程序运行时访问、检测和修改它本身状态,比如在程序运行时可以检查变量的类型和值,调用它们的方法,甚至修改它们的值。使用反射可以增加程序的灵活性,简单来说,反射就是程序在运行时能够检测自身和修改自身的一种能力。

IT杨秀才的博客 2158

【知识分享】车载以太网PCB布局设计指南

本文将深入探讨在设计车载以太网设备的印刷电路板(PCB)时需要着重考虑的若干关键主题。这些主题广泛涵盖了数据布线设计,以确保其符合信号完整性(SI)和电磁兼容性(EMC)标准;外部元器件的合理摆放;以及采取有效的抗电磁干扰(EMI)布局策略等方面。通过全面且细致地处理这些关键环节,能够显著提升车载以太网设备的性能与可靠性。

一只豌豆象的博客 1352

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

go语言快速入门:go的反射机制(22)

Java的反射机制是其标志性的特征之一,正是这种语言本身支持的强大的机制使得很多流行的框架有了用武之地。C++中虽然也能实现,但是语言本身并没有提供标准的支持。 而作为一门现代的语言,go语言也引入了反射机制,在这篇文章中我们将会了解一下go语言中的反射机制是如何使用的。

知行合一 止于至善 1万+

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

【golang/go语言】Go语言之反射

在计算机科学中,反射是指计算机程序在运行时(run time)可以访问、检测和修改它本身状态和行为的一种能力。用比喻来说,反射就是程序在运行的时候能够观察并修改自己的行为。

zuzhiang的博客 927

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

golang语言基础【3】反射

这是本人学习https://draven.co/golang/docs/part2-foundation 的随笔。

zasx1994的专栏 1134

Go语言——反射

文章目录一、reflect-反射-浅析-重要Go语言中的类型反射第一定律:反射可以将“接口类型变量”转换为“反射类型对象”反射第二定律:反射可以将“反射类型对象”转换为“接口类型变量”反射第三定律:如果要修改“反射类型对象”其值必须是“可写的”结构体总结二、reflect-反射类型对象-获取类型信息1. TypeOf()和reflect.Type类型1.1 理解反射的类型(Type)与种类(Kind)1.1.1 反射种类(Kind)的定义1.1.2 从类型对象中获取类型名称和种类的例子1.2. refle

脑袋装水的博客 1566

go语言中的反射

反射是指在程序运行期间对程序本身进行访问和修改的能力。正常情况程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。Golang 中反射可以实现以下功能:反射可以在程序运行期间动态的获取变量的各种信息,比如变量的类型 类别。

SunnyRivers 1173

Go语言基础之反射

文章目录Go语言基础之反射变量的内在机制反射介绍reflect包TypeOftype name和type kindValueOf通过反射获取值通过反射设置变量的值isNil()和isValid()isNil()isValid()举个例子结构体反射与结构体相关的方法StructField类型结构体反射示例反射是把双刃剑 Go语言基础之反射 本文介绍了Go语言反射的意义和基本使用。 变量的内在机制 G...

Allen 1万+

【Go语言反射reflect】

不同语言的反射模型不尽相同,有些语言还不支持反射。《Go 语言圣经》中是这样定义反射的:Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。为什么要用反射。

m0_46168848的博客 1311

12_Go语言 反射详解

1. 反射定义: 可以在运行时动态获取变量的相关信息。 Import (“reflect”) 官方对此有个非常简明的介绍,两句话耐人寻味: 反射提供一种让程序检查自身结构的能力。再精确点的描述是“反射是一种检查interface变量的底层类型和值的机制”; 反射是困惑的源泉;(想深入了解反射,必须深入理解类型和接口概念。) 2. 方法: reflect.TypeOf,获取变量的类型,返回re...

数据知道的博客 3万+

Go语言 反射

Go语言 反射

代码改变世界 1075
上一篇: 深入理解 ceph mgr
下一篇: 分布式存储Ceph之PG状态详解
「已注销」
博客等级 码龄6年 395粉丝 158原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值