如何使用 Go 的类型别名让自己的模型与 protobufs 一起工作?
How to use Go's type alias to make own models work with protobufs?
我有一些 REST API,我的模型定义为 Go 结构。
type User struct {
FirstName string
LastName string
}
然后我就有了获取数据的数据库方法。
GetUserByID(id int) (*User, error)
现在我想用 https://github.com/twitchtv/twirp 替换我的 REST API 。
因此我开始在 .proto
个文件中定义我的模型。
message User {
string first_name = 2;
string last_name = 3;
}
现在我有两种 User
类型。我们称它们为 native 和 proto 类型。
我还在我的 .proto
文件中定义了一个服务,returns 一个用户到前端。
service Users {
rpc GetUser(Id) returns (User);
}
这会生成一个我必须填写的界面。
func (s *Server) GetUser(context.Context, id) (*User, error) {
// i'd like to reuse my existing database methods
u, err := db.GetUserByID(id)
// handle error
// do more stuff
return u, nil
}
不幸的是,这不起作用。我的数据库 returns 一个 native 用户,但界面需要一个 proto 用户。
有没有简单的方法让它工作?也许使用 type aliases
?
非常感谢!
解决问题的一种方法是手动进行转换。
type User struct {
FirstName string
LastName string
}
type protoUser struct {
firstName string
lastName string
}
func main() {
u := db() // Retrieve a user from a mocked db
fmt.Println("Before:")
fmt.Printf("%#v\n", *u) // What db returns (*protoUser)
fmt.Println("After:")
fmt.Printf("%#v\n", u.AsUser()) // What conversion returns (User)
}
// Mocked db that returns pointer to protoUser
func db() *protoUser {
pu := protoUser{"John", "Dough"}
return &pu
}
// Conversion method (converts protoUser into a User)
func (pu *protoUser) AsUser() User {
return User{pu.firstName, pu.lastName}
}
The key part is the AsUser
method on the protoUser
struct.
There we simply write our custom logic for converting a protoUser
into a User
type we want to be working with.
Working Example
如评论区@Peter所述
我看过一个项目,该项目使用自定义 Convert
函数。它通过 json.Unmarshal
将 Protobuf 转换为本地结构,不确定性能如何,但这是一个可行的方法。
预览代码 PLAYGROUND
// Convert converts the in struct to out struct via `json.Unmarshal`
func Convert(in interface{}, out interface{}) error {
j, err := json.Marshal(in)
if err != nil {
return err
}
err = json.Unmarshal(j, &out)
if err != nil {
return err
}
return nil
}
func main() {
// Converts the protobuf struct to local struct via json.Unmarshal
var localUser User
if err := convert(protoUser, &localUser); err != nil {
panic(err)
}
}
输出
Before:
main.ProtoUser{FirstName:"John", LastName:"Dough"}
After:
main.User{FirstName:"John", LastName:"Dough"}
Program exited.
我有一些 REST API,我的模型定义为 Go 结构。
type User struct {
FirstName string
LastName string
}
然后我就有了获取数据的数据库方法。
GetUserByID(id int) (*User, error)
现在我想用 https://github.com/twitchtv/twirp 替换我的 REST API 。
因此我开始在 .proto
个文件中定义我的模型。
message User {
string first_name = 2;
string last_name = 3;
}
现在我有两种 User
类型。我们称它们为 native 和 proto 类型。
我还在我的 .proto
文件中定义了一个服务,returns 一个用户到前端。
service Users {
rpc GetUser(Id) returns (User);
}
这会生成一个我必须填写的界面。
func (s *Server) GetUser(context.Context, id) (*User, error) {
// i'd like to reuse my existing database methods
u, err := db.GetUserByID(id)
// handle error
// do more stuff
return u, nil
}
不幸的是,这不起作用。我的数据库 returns 一个 native 用户,但界面需要一个 proto 用户。
有没有简单的方法让它工作?也许使用 type aliases
?
非常感谢!
解决问题的一种方法是手动进行转换。
type User struct {
FirstName string
LastName string
}
type protoUser struct {
firstName string
lastName string
}
func main() {
u := db() // Retrieve a user from a mocked db
fmt.Println("Before:")
fmt.Printf("%#v\n", *u) // What db returns (*protoUser)
fmt.Println("After:")
fmt.Printf("%#v\n", u.AsUser()) // What conversion returns (User)
}
// Mocked db that returns pointer to protoUser
func db() *protoUser {
pu := protoUser{"John", "Dough"}
return &pu
}
// Conversion method (converts protoUser into a User)
func (pu *protoUser) AsUser() User {
return User{pu.firstName, pu.lastName}
}
The key part is the
AsUser
method on theprotoUser
struct.
There we simply write our custom logic for converting aprotoUser
into aUser
type we want to be working with.
Working Example
如评论区@Peter所述
我看过一个项目,该项目使用自定义 Convert
函数。它通过 json.Unmarshal
将 Protobuf 转换为本地结构,不确定性能如何,但这是一个可行的方法。
预览代码 PLAYGROUND
// Convert converts the in struct to out struct via `json.Unmarshal`
func Convert(in interface{}, out interface{}) error {
j, err := json.Marshal(in)
if err != nil {
return err
}
err = json.Unmarshal(j, &out)
if err != nil {
return err
}
return nil
}
func main() {
// Converts the protobuf struct to local struct via json.Unmarshal
var localUser User
if err := convert(protoUser, &localUser); err != nil {
panic(err)
}
}
输出
Before:
main.ProtoUser{FirstName:"John", LastName:"Dough"}
After:
main.User{FirstName:"John", LastName:"Dough"}
Program exited.