在结构体中有多种条件赋值属性的正确方式是什么?

huangapple go评论77阅读模式
英文:

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(&amp;us.FirstName, input.FirstName)
mergef(&amp;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 &lt; 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)

huangapple
  • 本文由 发表于 2023年1月24日 13:56:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/75217886.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定