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.)
To use the REST API to update existing model objects, complete one of the following two methods: Use the model object resource representing the existing object and the HTTP PUT method, passing the new object data in the body of the request.
You may use the POST method to create, update and delete resources but this is considered a poor design. The different http verbs standardize modern ways of creating REST APIs.
Conventionally, updating an object like this via a REST API is done by sending a PATCH or PUT to /people/123 with the new state of the object. However, you could potentially be doing one or many things in your update: Change the person's name. Update an existing address.
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}
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With