英文:
What is the right way to conditionally assign multiple properties to a struct
问题
我正在为我用Go语言编写的后端编写一个GraphQL查询的解析器函数。在解析器中,我有用户数据,我想使用包含多个可能更新属性的输入值来更新它。
在JavaScript中,可以通过解构快速完成此操作(伪代码):
const mergedObj = {...oldProps, ...newProps}
目前,我的解析器函数如下所示(使用gqlgen用于GraphQL Go解析器):
func (r *mutationResolver) ModifyUser(ctx context.Context, input *model.ModifyUserInput) (*model.User, error) {
id := input.ID
us, ok := r.Resolver.UserStore[id]
if !ok {
return nil, fmt.Errorf("not found")
}
if input.FirstName != nil {
us.FirstName = *input.FirstName
}
if input.LastName != nil {
us.LastName = *input.LastName
}
if input.ProfileImage != nil {
us.ProfileImage = input.ProfileImage
}
if input.Password != nil {
us.Password = *input.Password
}
if input.Email != nil {
us.Email = *input.Email
}
if input.InTomorrow != nil {
us.InTomorrow = input.InTomorrow
}
if input.DefaultDaysIn != nil {
us.DefaultDaysIn = input.DefaultDaysIn
}
r.Resolver.UserStore[id] = us
return &us, nil
}
这种写法感觉有点样板化。在这种情况下,通过迭代结构体键是否有意义?还是我漏掉了其他模式?
英文:
I'm working on a resolver function for a GraphQL query for a BE I'm writing in Go. In the resolver, I have user data that I want to update, using an input value containing several possible update properties.
In JavaScript, this can be done quickly through destructuring (pseudo):
const mergedObj = {...oldProps, ...newProps}
For now, my resolver function looks like this (using gqlgen for GraphQL Go resolvers):
func (r *mutationResolver) ModifyUser(ctx context.Context, input *model.ModifyUserInput) (*model.User, error) {
id := input.ID
us, ok := r.Resolver.UserStore[id]
if !ok {
return nil, fmt.Errorf("not found")
}
if input.FirstName != nil {
us.FirstName = *input.FirstName
}
if input.LastName != nil {
us.LastName = *input.LastName
}
if input.ProfileImage != nil {
us.ProfileImage = input.ProfileImage
}
if input.Password != nil {
us.Password = *input.Password
}
if input.Email != nil {
us.Email = *input.Email
}
if input.InTomorrow != nil {
us.InTomorrow = input.InTomorrow
}
if input.DefaultDaysIn != nil {
us.DefaultDaysIn = input.DefaultDaysIn
}
r.Resolver.UserStore[id] = us
return &us, nil
}
This feels quite boilerplatey. Would it make sense in this situation to iterate through struct keys? Or is there another pattern I'm missing?
答案1
得分: 5
使用一个函数来减少样板代码:
func mergef[T any](a, b *T) {
if b != nil {
*a = *b
}
}
...
mergef(&us.FirstName, input.FirstName)
mergef(&us.LastName, input.LastName)
...
使用反射包来进一步减少样板代码:
// merge 将指向 s 的结构体中的字段设置为指向 d 的结构体中的解引用字段。
//
// 参数 s 必须指向具有指针类型字段的结构体。
// 参数 d 必须指向与 s 中的字段对应的字段的结构体:
// d 中必须有与 s 中的字段同名的字段;s 中的字段的类型必须是 d 中字段的类型的指针。
func merge(d, s any) {
sv := reflect.ValueOf(s).Elem()
dv := reflect.ValueOf(d).Elem()
for i := 0; i < sv.NumField(); i++ {
sf := sv.Field(i)
if sf.IsNil() {
continue
}
df := dv.FieldByName(sv.Type().Field(i).Name)
df.Set(sf.Elem())
}
}
像这样使用该函数:
merge(us, input)
英文:
Use a function to reduce the boilerplate:
func mergef[T any](a, b *T) {
if b != nil {
*a = *b
}
}
...
mergef(&us.FirstName, input.FirstName)
mergef(&us.LastName, input.LastName)
...
Use the reflect package to reduce more boilerplate:
// merge sets fields in struct pointed to by d to
// dereferenced fields in struct pointed to by s.
//
// Argument s must point to a struct with pointer type
// fields.
// Argument d must point to a struct with fields that
// correspond to the fields in s: there must be a field
// in d with the same name as a field in s; the type of
// the field in s must be a pointer to the type of the field
// in d.
func merge(d, s any) {
sv := reflect.ValueOf(s).Elem()
dv := reflect.ValueOf(d).Elem()
for i := 0; i < sv.NumField(); i++ {
sf := sv.Field(i)
if sf.IsNil() {
continue
}
df := dv.FieldByName(sv.Type().Field(i).Name)
df.Set(sf.Elem())
}
}
Employ the function like this:
merge(us, input)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论