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