从REST API调用更新对象-结构合并?

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.)

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.

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 Models. 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.

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}.