从REST API调用更新对象 - struct merge?

时间:2015-05-27 18:33:32

标签: go

我有一个接受稀疏更新的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)
}

(为清楚起见,错误处理已被省略。)

3 个答案:

答案 0 :(得分:6)

好吧,如果您愿意使用反射,那么问题就变得非常简单了:

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
}

我们reflect.ValueOf(&m3).Elem()的原因是v3需要设置,请参阅http://blog.golang.org/laws-of-reflection

但基本上,通过使用反射,我们可以循环遍历struct字段,查看更新的字段是否为nil,如果是,则使用旧值。

答案 1 :(得分:3)

如果您不想使用反射,另一个选项是从数据库中检索对象并将其地址传递给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},并将{"completed":true}之类的内容发送到您的更新方法(/todos/1,其中1是todoId),那么您最终会得到这个:{"ID":1,"name":"Do stuff","completed":true}

答案 2 :(得分:2)

使用反射

您可以使用反射和the reflect package更新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)来调用此方法,xyModel。调用此函数后,x将被修改。

样本输出

&#34;合并&#34;以下,

{  
   "id":"1",
   "field1":"one",
   "field2":"two",
   "field3":"three"
}
{  
   "id":"1",
   "field3":"THREE"
}    

的产率:

{  
   "id":"1",
   "field1":"one",
   "field2":"two",
   "field3":"THREE"
}

也就是说,它会覆盖旧结构中存在的所有值,忽略&#34; undefined&#34;。

显然,你可以随意支持(或不支持)。

完成程序

See a complete, working example at the Go Playground.

正常警告(检查生产中返回的错误!)适用。