Go语言多态实践以及gin框架c.BindJSON序列化遇到的坑
遇到的问题
如果定义的接收结构体字段是interface{},在调用gin的 c.BindJSON 方法后会直接转为map, 导致无法断言为其他类型
场景
在创建工程请求中,根据工程类别的不同会有多种创建参数,比如
// A 类型需要编译 所以有这些字段
type ProjTypeA struct{buildCmd string `json:"build_cmd"` // 编译命令buildScript string `json:"build_script"` // 编译脚本
}// B 类型直接执行,需要这些字段
type ProjTypeB struct{ExecCmd string `json:"exec_cmd"` // 执行命令
}
因为以后可能会扩展出更多类型的工程,所以考虑用多态来实现对工程子参数的处理,这样以后新增工程类型只需要新类型实现TypePara接口即可,尽量减少对主干代码的修改。甚至无需修改creatproj函数,只要修改接口里的判断方法就行(因为前端请求过来的json要通过业务逻辑判断后才能知道反序列化为哪个工程类型)
前端传请求体时会把这些不同的结构统一写入TypePara这个字段中,这里的处理有两种情况:
- 在后端接收时这个字段设为interface{},用下方定义的PostProjReq来接收。
type PostProjReq struct {Name string `json:"name"`Desc string `json:"desc"`TypePara interface{} `json:"type_para"`
}
此时再通过c.BindJSON接收
pp := new(api.PostProjReq)if err := c.BindJSON(*pp); err != nil {log.Println("json unmarshal error: ", err)c.JSON(http.StatusOK, api.HttpRespBody{Code: 400,Msg: "",Data: "",})return}
可以通过,这里是因为interface{}(包含TypePara是interface{}的情况,就是空接口,没有实现方法) 就会自动转为map
TypePara:map[arch:3 file_id: 1 os:3 envi:envi1 exec_cmd:mycmd]
但问题是,map无法再被断言为具体的ProjPara类型
- 如果改成自己定义的接口类型TypePara
// 统一接口
type TypePara interface {typePara2json() []bytegetTarget() stringunmarshalJSON(data []byte)handlerFile(item *repo.Proj, target string)
}
并且用TypePara来接收:
type PostProjReq struct {Name string `json:"name"`Desc string `json:"desc"`TypePara TypePara `json:"type_para"`
}
就会直接在c.BindJSON这一步报错:
json: cannot unmarshal object into Go struct field PostProjReq.type_para of type service.TypePara
这个可能的原因有:
由于接口类型的动态类型是在运行时才能确定的,而 JSON 解析需要在编译时确定数据类型和结构,因此无法直接将 JSON 解析到接口类型上
处理方式:先将一个字段设置为json.RawMessage暂存,然后根据其他条件推断
只要将json转换为了typepara,不管是哪种类型,都能够进行switch断言判断,可以不用ifelse了。
如果一定想要以interface{}接收并不转为map,除非自己实现UnmarshalJSON,但是接口类型不能作为接收器,这个思路还没有走通。
最后的实现方式:对于前端传过来的请求体,先用一个TypeParaJSON字段,json.RawMessge类型来接收,
// 前端传入创建工程表单
type PostProjReq struct {Name string `json:"name"`Desc string `json:"desc"`ProjType string `json:"proj_type"` // 指明这是什么类型的工程TypePara TypePara // 不需要请求体直接unmarshal进来TypeParaJSON json.RawMessage `json:"type_para"` // 单独解析
}
然后在TypePara2JSON()函数中根据其他条件判断类型(代替直接断言),判断好了之后调相应类型的方法来完成构造工作:
// 将req转换为存库的json
func TypePara2JSON(pp *PostProjReq) []byte {if pp.ProjType == "A" { // A类型的工程p := &ProjTypeParaA{}p.unmarshalJSON(pp.TypeParaJSON)pp.TypePara = p // 赋值给这个结构,供后续处理使用,后续可以直接断言这个字段了 pp.TypePara.(*ProjTypeParaA)return p.typePara2json() // 这里是业务逻辑有需求要再次处理成json,如果没有需求,那么可以直接返回TypePara 后面会写} else if pp.ProjType == "B" { // B类型的工程p := &ProjTypeParaB{}p.unmarshalJSON(pp.TypeParaJSON)pp.TypePara = p // 赋值return p.typePara2json()} else if pp.ProjType == "C" { // C类型的工程p := &ProjTypeParaC{}p.unmarshalJSON(pp.TypeParaJSON)pp.TypePara = preturn p.typePara2json()} else {logrus.Error(" unsupport type")}return []byte{}
}
另外解释下为啥要继续处理成json,因为存入数据库的json字段类型是datatypes.JSON 而请求解析过来的是json.RawMessage,这两个类型不能直接赋值
SubProjPara: dto.TypeParaJSON // 不能直接赋值
如果把TypePara改为[]byte也不行,会显示:
json: cannot unmarshal object into Go struct field PostProjReq.type_para of type []uint8
所以还需要这么json.RawMessage->ProjTypeX->datatypes.JSON的转换流程
数据库表结构:
type Proj struct{ID stringName stringDesc stringSubProjPara datatypes.JSON `gorm:"type:json;"` // 就是TypePara
}
从数据库获取数据并转成相应的TypePara子类型的方法:
// 将数据库中获取的json转为typepara
func ConstructSubPara(pp *models.Proj) TypePara {if pp.ProjType == "A" { // A类型的工程p := &ProjTypeParaA{}p.unmarshalJSON(pp.SubProjPara)return p} else if pp.ProjType == "B" { // B类型的工程p := &ProjTypeParaB{}p.unmarshalJSON(pp.SubProjPara)return p} else if pp.ProjType == "C" { // C类型的工程p := &ProjTypeParaC{}p.unmarshalJSON(pp.SubProjPara)return p} else {logrus.Error(" unsupport type")}return nil
}
此后就可以直接调用这个接口的其他方法了,比如还有一个方法 handleFile,那么直接
ty:=ConstructSubPara(item) // item就是Proj对象
HandleFile(tp) //
func HandleFile(tp TypePara){tp.handleFile() // 这里就是调每个类型自己的handleFile实现了,而且也不用手动处理是什么类型 完全利用到了多态
}
相关文章:
Go语言多态实践以及gin框架c.BindJSON序列化遇到的坑
遇到的问题 如果定义的接收结构体字段是interface{},在调用gin的 c.BindJSON 方法后会直接转为map, 导致无法断言为其他类型 场景 在创建工程请求中,根据工程类别的不同会有多种创建参数,比如 // A 类型需要编译 所以有这些字…...
SpringCloud神领物流学习笔记:项目概述(一)
SpringCloud神领物流学习笔记:项目概述(一) 文章目录 SpringCloud神领物流学习笔记:项目概述(一)1、项目介绍2、基本业务流程3、系统架构4、技术架构 1、项目介绍 神领物流是一个基于微服务架构体系的【…...
RocketMQ异步报错:No route info of this topic
在SpringBoot中发送RocketMQ异步消息的时候报错了,提示org.apache.rocketmq.client.exception.MQClientException: No route info of this topic, testTopic1 这里给出具体的解决方案 一、Broker模块不支持自动创建topic,并且topic没有被手动创建过 R…...
Node.js学习记录(一)
目录 一、文件读取 readFile 二、写入文件 writeFile 三、动态路径 __dirname:表示当前文件所处的目录、path.join 四、获取路径文件名 path.basename 五、提取某文件中的css、JS、html 六、http 七、启动创建web服务器 服务器响应 八、将资源请求的 url 地…...
【AI】Pytorch_模型构建
建议点赞收藏关注!持续更新至pytorch大部分内容更完。 本文已达到10w字,故按模块拆开,详见目录导航。 整体框架如下 数据及预处理 模型及其构建 损失函数及优化器 本节目录 模型线性回归逻辑回归LeNetAlexNet 构建模块组织复杂网络初始化网络…...
FFmpeg源码:avcodec_descriptor_get函数分析
一、avcodec_descriptor_get函数的声明 avcodec_descriptor_get函数声明在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的头文件libavcodec/codec_desc.h中: /*** return descriptor for given codec ID or NULL if no descriptor exist…...
为数据仓库构建Zero-ETL无缝集成数据分析方案(下篇)
对于从事数据分析的小伙伴们来说,最头疼的莫过于数据处理的阶段。在我们将数据源的原始数据导入数据仓储进行分析之前,我们通常需要进行ETL流程对数据格式进行统一转换,这个流程需要分配专业数据工程师基于业务情况完成,整个过程十…...
ElMessageBox消息确认框组件在使用时如何设置第三个或多个自定义按钮
ElMessageBox自带两个按钮一个确认一个取消,当还想使用该组件还想再加个功能组件时,就需要自定义个按钮加到组件里 第二种方法可以通过编写自定义弹窗来完成,个人觉得代码量增多过于繁琐,当然也可以实现 先定义方法负责获取dom父节点,创建新的子元素加…...
javaWeb【day04】--(MavenSpringBootWeb入门)
01. Maven课程介绍 1.1 课程安排 学习完前端Web开发技术后,我们即将开始学习后端Web开发技术。做为一名Java开发工程师,后端Web开发技术是我们学习的重点。 1.2 初识Maven 1.2.1 什么是Maven Maven是Apache旗下的一个开源项目,是一款用于…...
[Linux]:文件(下)
✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 重定向原理 在明确了文件描述符的概念及其分配规则后,我们就可…...
【学习笔记】手写Tomcat 一
目录 HTTP协议请求格式 HTTP协议响应格式 Socket 解读代码 服务端优化 解读代码 作业 1. 响应一个 HTML 页面给客户端,游览器把接收到的内容进行渲染 2. 文件的媒体类型是写死的,肯定不行,怎么变成动态? 昨天作业答案 …...
springboot基础-Druid数据库连接池使用
文章目录 引入Druid组件(maven)配置数据源Druid配置项1. 数据源配置2. 监控配置3. 安全配置4. SQL拦截配置 示例配置相关地址 Druid是阿里巴巴开源的一个高性能的Java数据库连接池组件,它提供了强大的监控统计功能和工具支持。Druid不仅可以作…...
C语言文件操作全攻略:从打开fopen到读写r,w,一网打尽
前言 在C语言中,文件操作是一项基础而强大的功能,它允许程序与存储在硬盘上的数据进行交互。无论是读取配置文件、处理日志文件,还是创建新的数据文件,C语言都提供了丰富的函数库来支持这些操作。本文将整合并详细介绍fopen(), 对…...
【0328】Postgres内核之 “User ID state”
1. User ID state 我们必须追踪与“用户ID(user ID)”概念相关的多个不同值。Postgres内核中有共有以下几个 User ID。 ✔ AuthenticatedUserId ✔ SessionUserId ✔ OuterUserId ✔ CurrentUserId 1.1 User ID 概念相关的不同值 AuthenticatedUserId AuthenticatedUserId…...
VisualStudio环境搭建C++
Visual Studio环境搭建 说明 C程序编写中,经常需要链接头文件(.h/.hpp)和源文件(.c/.cpp)。这样的好处是:控制主文件的篇幅,让代码架构更加清晰。一般来说头文件里放的是类的申明,函数的申明,全局变量的定义等等。源…...
linux 文件压缩并且切割压缩
Linux系统中,split命令是一个非常实用的工具,它可以将一个大文件分割成多个小文件 1、先将文件压缩 tar -cvf access.log.tar.gz access2、将文件压缩为每500mb一个文件,-b 500m 指定了每个分割文件的大小为500MB,-d 表示使用数字…...
支持iPhone 16新品预售,饿了么同步上线专人配送等特色服务
9月10日凌晨,2024年 Apple 秋季新品发布会上正式揭晓iPhone 16新机。9月10日一早,饿了么同步宣布:今年将携手近4000家Apple 授权专营店,支持iPhone 16新品预售及现货的同步开售。新机现货首发当日,饿了么消费者最快半小…...
低光增强效果展示
训练模型给图片加标题...
李诞-2021.8脱口秀工作手册-11-pitch your idea把一个想法扎进别人脑子里;专业,做足准备,给选择option!
17 每个人都该学会卖掉自己的想法 要把一件事办妥,就要有把一个想法扎进别人脑子里的决心。 很早之前,我跟编剧鬼顾达去见一个非常非常不好合作的嘉宾,我们本来带去了一份很好的稿子,他不愿意接受,反复抗议ÿ…...
vue3 自定义指令 directive
1、官方说明:https://cn.vuejs.org/guide/reusability/custom-directives 除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。 我们已经介绍了两种在 Vue 中重用代码的方式:组件和…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
RLHF vs RLVR:对齐学习中的两种强化方式详解
在语言模型对齐(alignment)中,强化学习(RL)是一种重要的策略。而其中两种典型形式——RLHF(Reinforcement Learning with Human Feedback) 与 RLVR(Reinforcement Learning with Ver…...
标注工具核心架构分析——主窗口的图像显示
🏗️ 标注工具核心架构分析 📋 系统概述 主要有两个核心类,采用经典的 Scene-View 架构模式: 🎯 核心类结构 1. AnnotationScene (QGraphicsScene子类) 主要负责标注场景的管理和交互 🔧 关键函数&…...
