英文:
Updating objects from REST API calls - struct merge?
问题
我有一个接受稀疏更新的 JSON REST API,但是我想出的模式似乎非常冗长。我是不是走错了路?
(假设这是使用没有内置稀疏更新支持的数据存储。)
func choose(a, b *string) *string {
if a != nil {
return a
}
return b
}
type Model {
Id *string `json:"id"`
Field1 *string `json:"field1"`
Field2 *string `json:"field2"`
Field3 *string `json:"field3"`
...
}
func (m1 Model) Update(m2 Model) (m3 Model) {
m3.Id = choose(m2.Id, m1.Id)
m3.Field1 = choose(m2.Field1, m1.Field1)
m3.Field2 = choose(m2.Field2, m1.Field2)
m3.Field3 = choose(m2.Field3, m1.Field3)
...
return
}
func UpdateController(input Model) error {
previous, _ := store.Get(*input.Id)
updated := previous.Update(input)
return store.Put(updated)
}
理想情况下,我希望能像这样简化 UpdateController
函数:
func UpdateController(input Model) {
previous, _ := store.Get(*input.Id)
updated, _ := structs.Update(previous, input)
return store.Put(updated)
}
(为了清晰起见,省略了错误处理。)
英文:
I have a JSON REST API accepting sparse updates, but the pattern I've come up with seems exceptionally verbose. Am I going about this the wrong way?
(Assume this is using a data store with no sparse update support built in.)
func choose(a, b *string) *string {
if a != nil {
return a
}
return b
}
type Model {
Id *string `json:"id"`
Field1 *string `json:"field1"`
Field2 *string `json:"field2"`
Field3 *string `json:"field3"`
...
}
func (m1 Model) Update(m2 Model) (m3 Model) {
m3.Id = choose(m2.Id, m1.Id)
m3.Field1 = choose(m2.Field1, m1.Field1)
m3.Field2 = choose(m2.Field2, m1.Field2)
m3.Field3 = choose(m2.Field3, m1.Field3)
...
return
}
func UpdateController(input Model) error {
previous, _ := store.Get(*input.Id)
updated := previous.Update(input)
return store.Put(updated)
}
Ideally I'd be able to write UpdateController
like this instead:
func UpdateController(input Model) {
previous, _ := store.Get(*input.Id)
updated, _ := structs.Update(previous, input)
return store.Put(updated)
}
(Error-handling elided for clarity.)
答案1
得分: 7
好的,以下是翻译好的内容:
如果你愿意使用反射,这个问题就变得相当简单:
func (m1 Model) Update(m2 Model) (m3 Model) {
old := reflect.ValueOf(m1)
new := reflect.ValueOf(m2)
final := reflect.ValueOf(&m3).Elem()
for i := 0; i < old.NumField(); i++ {
if !new.Field(i).IsNil() {
final.Field(i).Set(new.Field(i))
} else {
final.Field(i).Set(old.Field(i))
}
}
return
}
我们之所以使用 reflect.ValueOf(&m3).Elem()
是因为 v3 需要可设置,参见 http://blog.golang.org/laws-of-reflection
基本上,通过使用反射,我们可以遍历结构体字段,查看更新后的字段是否为 nil,如果是,则使用旧值。
英文:
Well, if you are open to using reflection, the problem becomes fairly simple:
http://play.golang.org/p/dc-OnO1cZ4
func (m1 Model) Update(m2 Model) (m3 Model) {
old := reflect.ValueOf(m1)
new := reflect.ValueOf(m2)
final := reflect.ValueOf(&m3).Elem()
for i := 0; i < old.NumField(); i++ {
if !new.Field(i).IsNil() {
final.Field(i).Set(new.Field(i))
} else {
final.Field(i).Set(old.Field(i))
}
}
return
}
The reason we do reflect.ValueOf(&m3).Elem()
is that v3 needs to be settable, see http://blog.golang.org/laws-of-reflection
But basically, by using reflection, we can loop through the struct fields, see if the updated one is nil, and if so, we use the old value.
答案2
得分: 4
另一个选项是,如果你不想使用反射,可以从数据库中检索对象,并将其地址传递给JSON解码器。只有在更新方法的JSON中定义的字段才会被更新。然后,你可以使用普通的方法将更改保存到数据库中。以下是使用gin和gorm的示例代码。
func TodoUpdate(c *gin.Context) {
var todo Todo
todoId := c.Param("todoId")
DB.First(&todo, todoId)
if err := json.NewDecoder(c.Request.Body).Decode(&todo); err != nil {
log.Fatal("Update error. Error:", err.Error())
}
DB.Save(&todo)
}
所以,例如,如果你的数据库中有{"ID":1,"name":"Do stuff","completed":false}
,并且向你的更新方法(/todos/1
,其中1是todoId
)发送{"completed":true}
,你最终会得到{"ID":1,"name":"Do stuff","completed":true}
。
英文:
Another option, if you don't want to use reflection, is to retrieve the object from the database and pass it's address to the JSON decoder. Only the fields defined in the JSON of the update method will be updated. Then you can use a normal method to save the changes to the database. Here's a sample code using gin and gorm.
func TodoUpdate(c *gin.Context) {
var todo Todo
todoId := c.Param("todoId")
DB.First(&todo, todoId)
if err := json.NewDecoder(c.Request.Body).Decode(&todo); err != nil {
log.Fatal("Update error. Error:", err.Error())
}
DB.Save(&todo)
}
So, for example, if you have {"ID":1,"name":"Do stuff","completed":false}
in your database, and send something like {"completed":true}
to your update method (/todos/1
, where 1 is todoId
) you'll end up with this: {"ID":1,"name":"Do stuff","completed":true}
.
答案3
得分: 2
使用反射
您可以使用反射和reflect包来更新Model
。
以下函数会就地更新旧的Model
:
func (old *Model) MergeInPlace(new Model) {
for ii := 0; ii < reflect.TypeOf(old).Elem().NumField(); ii++ {
if x := reflect.ValueOf(&new).Elem().Field(ii); !x.IsNil() {
reflect.ValueOf(old).Elem().Field(ii).Set(x)
}
}
}
您可以通过调用x.MergeInPlace(y)
来使用此方法,其中x
和y
都是Model
。在调用此函数后,x
将被修改。
示例输出
对于以下内容的"合并":
{
"id":"1",
"field1":"one",
"field2":"two",
"field3":"three"
}
{
"id":"1",
"field3":"THREE"
}
得到的结果是:
{
"id":"1",
"field1":"one",
"field2":"two",
"field3":"THREE"
}
也就是说,它会覆盖旧结构中存在的所有值,忽略"未定义"的值。
显然,您可以根据需要进行编组(或不编组)。
完整程序
正常的注意事项(在生产中检查返回的错误!)也适用。
英文:
Use reflection
You can update the Model
using reflection, and the reflect
package.
The following function updates the old Model
in-place:
func (old *Model) MergeInPlace(new Model) {
for ii := 0; ii < reflect.TypeOf(old).Elem().NumField(); ii++ {
if x := reflect.ValueOf(&new).Elem().Field(ii); !x.IsNil() {
reflect.ValueOf(old).Elem().Field(ii).Set(x)
}
}
}
You would call this method by saying x.MergeInPlace(y)
, where x
and y
are Model
s. x
will be modified after you call this function.
Sample output
"Merging" the following,
{
"id":"1",
"field1":"one",
"field2":"two",
"field3":"three"
}
{
"id":"1",
"field3":"THREE"
}
yields:
{
"id":"1",
"field1":"one",
"field2":"two",
"field3":"THREE"
}
That is, it overwrites all values present in the new struct in the old one, ignoring values that are "undefined".
Obviously, you can Marshal (or not) as you please.
Complete program
See a complete, working example at the Go Playground.
The normal caveats (check returned errors in production!) apply.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论