【实践】给proto的message添加自定义tag

2024-05-14 07:36

本文主要是介绍【实践】给proto的message添加自定义tag,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

背景

通常来说, 使用proto定义message的Field是使用下划线,比如:

# proto定义
message Req {string key_name = 1;
}# 生成的.pb.go 中req的定义type Req struct {state         protoimpl.MessageStatesizeCache     protoimpl.SizeCacheunknownFields protoimpl.UnknownFieldsKeyName            string            `protobuf:"bytes,1,opt,name=key_name,json=keyName,proto3" json:"key_name,omitempty"`

注意: proto的定义,现在默认推荐proto3, 所以此处忽略 proto2中可以给字段声明optional和required。

不过,需要特别注意一下tag中如果添加了omitempty选项,proto2和proto3中对于默认值的处理有所不同。

比如:proto2中对于添加了optional的bool类型,生成的字段是个指针类型,默认值为nil才是empty,false就不是默认值了,而对于proto3中bool的定义,默认值就是false,对于添加omitempty的tag来说就是空了(切记)

从生产的pb.go文件可以看到, 默认生成的json tag是根据字段名的定义来的

可能你会疑惑: protobuf声明的tag中有name=key_name和json=keyName的两者,这应该是用在不同marshal和unMarshal的场景, 比如之前提到的jsonpb库,可以指定marshal的选项: OrigName= true / fasle: 

import ("github.com/gogo/protobuf/jsonpb""encoding/json"
)marshaler := &jsonpb.Marshaler{OrigName:     false,  // 是否按字段的声明(.proto) nameEnumsAsInts:  true,EmitDefaults: true,}# 一个req 示例
req := &Req {KeyName: "123",
}# OrigName=false
out, _ := marshaler.MarshalToString(req) 
t.Logf("out of jsopb:%v\n", out). // {"keyName":"123"}outJson, _ := json.Marshal(req)
t.Logf("out of json:%v\n", string(outJson)) // {"key_name":"123"}# OrigName=true
marshaler = jsonpb.Marshaler{OrigName: true,}out, _ = marshaler.MarshalToString(req)
t.Logf("out of jsopb:%v\n", out). // {"key_name":"123"}
  • OrigName= true:  此时就是按照protobuf声明的tag中name=key_name来处理的
  • OrigName= false:   此时就是按照protobuf声明的tag中json=keyName来处理的

 

到此阶段, pb能生成的基本就是这样的。那如果我想添加自定义的tag呢? 

比如: keyName 映射成 key_real_name 等, 这样的需求应该还不少

添加自定义tag

可以使用这个工具来给Field添加

先安装一下:

go install github.com/favadi/protoc-go-inject-tag@latest

修改对应的proto定义, 然后执行:protoc-go-inject-tag -input="./*.pb.go"

message Req {string key_name = 1; // @gotags: json:"key_real_name" bson:"key_in_monogo" orm:"key_in_orm"
}# 对应的pb生产文件:
type Req struct {state         protoimpl.MessageStatesizeCache     protoimpl.SizeCacheunknownFields protoimpl.UnknownFieldsKeyName string `protobuf:"bytes,1,opt,name=key_name,json=keyName,proto3" json:"key_real_name" bson:"key_in_monogo" orm:"key_in_orm"` // @gotags: json:"key_real_name" bson:"key_in_monogo" orm:"key_in_orm"
}

可以看到,通过@gotags 自定义的tag声明, 生效了。

1. json的tag可以指定新的名称

2. 添加了用于映射mongodb中字段的bson tag

3. 同时, 还添加了orm组件的字段tag 映射名称

是不是很方便, Enjoy ~

小技巧

问题: 如果想要复用proto的message定义, 一般可以直接在proto中引用对应message的定义即可, 那如果我不行新增一个层级呢?

正常使用情况:

message Req {string key_name = 1; // @gotags: json:"key_real_name" bson:"key_in_monogo" orm:"key_in_orm"
}message ReqMeta {string meta = 1;
}// 引用Req和ReqMeta
message ReqAndMeta {Req req = 1;ReqMeta meta = 2;
}

 如果是这样, json格式为:

{"req":{"key_real_name":"123"},"meta":{"meta":"meta123"}}

 

如果这就是你想要的, 那通过在proto中定义一下就可以了。

如果你不想要新增一个req和meta层级呢?

直接在proto中引用定义肯定不行, 但是可以使用golang的特性: 组合

type ReqAndMetaCombine struct {*Req*ReqMeta
}

通过组合多个proto中的message,最后的json格式如下:

{"key_real_name":"123","meta":"meta123"}

确实少了一个层级, it works ~

 

这篇关于【实践】给proto的message添加自定义tag的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/988141

相关文章

Java 的ArrayList集合底层实现与最佳实践

《Java的ArrayList集合底层实现与最佳实践》本文主要介绍了Java的ArrayList集合类的核心概念、底层实现、关键成员变量、初始化机制、容量演变、扩容机制、性能分析、核心方法源码解析、... 目录1. 核心概念与底层实现1.1 ArrayList 的本质1.1.1 底层数据结构JDK 1.7

JDK21对虚拟线程的几种用法实践指南

《JDK21对虚拟线程的几种用法实践指南》虚拟线程是Java中的一种轻量级线程,由JVM管理,特别适合于I/O密集型任务,:本文主要介绍JDK21对虚拟线程的几种用法,文中通过代码介绍的非常详细,... 目录一、参考官方文档二、什么是虚拟线程三、几种用法1、Thread.ofVirtual().start(

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础

springboot依靠security实现digest认证的实践

《springboot依靠security实现digest认证的实践》HTTP摘要认证通过加密参数(如nonce、response)验证身份,避免明文传输,但存在密码存储风险,相比基本认证更安全,却因... 目录概述参数Demopom.XML依赖Digest1Application.JavaMyPasswo

分析 Java Stream 的 peek使用实践与副作用处理方案

《分析JavaStream的peek使用实践与副作用处理方案》StreamAPI的peek操作是中间操作,用于观察元素但不终止流,其副作用风险包括线程安全、顺序混乱及性能问题,合理使用场景有限... 目录一、peek 操作的本质:有状态的中间操作二、副作用的定义与风险场景1. 并行流下的线程安全问题2. 顺

Java 结构化并发Structured Concurrency实践举例

《Java结构化并发StructuredConcurrency实践举例》Java21结构化并发通过作用域和任务句柄统一管理并发生命周期,解决线程泄漏与任务追踪问题,提升代码安全性和可观测性,其核心... 目录一、结构化并发的核心概念与设计目标二、结构化并发的核心组件(一)作用域(Scopes)(二)任务句柄

C#中通过Response.Headers设置自定义参数的代码示例

《C#中通过Response.Headers设置自定义参数的代码示例》:本文主要介绍C#中通过Response.Headers设置自定义响应头的方法,涵盖基础添加、安全校验、生产实践及调试技巧,强... 目录一、基础设置方法1. 直接添加自定义头2. 批量设置模式二、高级配置技巧1. 安全校验机制2. 类型

Java中的Schema校验技术与实践示例详解

《Java中的Schema校验技术与实践示例详解》本主题详细介绍了在Java环境下进行XMLSchema和JSONSchema校验的方法,包括使用JAXP、JAXB以及专门的JSON校验库等技术,本文... 目录1. XML和jsON的Schema校验概念1.1 XML和JSON校验的必要性1.2 Sche

SpringBoot集成WebService(wsdl)实践

《SpringBoot集成WebService(wsdl)实践》文章介绍了SpringBoot项目中通过缓存IWebService接口实现类的泛型入参类型,减少反射调用提升性能的实现方案,包含依赖配置... 目录pom.XML创建入口ApplicationContextUtils.JavaJacksonUt

MyCat分库分表的项目实践

《MyCat分库分表的项目实践》分库分表解决大数据量和高并发性能瓶颈,MyCat作为中间件支持分片、读写分离与事务处理,本文就来介绍一下MyCat分库分表的实践,感兴趣的可以了解一下... 目录一、为什么要分库分表?二、分库分表的常见方案三、MyCat简介四、MyCat分库分表深度解析1. 架构原理2. 分