Updating objects from REST API calls - merging structures?

I have a JSON REST API that allows sparse updates, but the template I came up with looks extremely detailed. Am I going about it wrong?

(Suppose this is using a data warehouse without native sparse update support.)

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 could write UpdateController as follows:

 func UpdateController(input Model) { previous, _ := store.Get(*input.Id) updated, _ := structs.Update(previous, input) return store.Put(updated) } 

(Processing error resolved for clarity.)

+6
source share
3 answers

Well, if you are open to using reflection, the problem becomes quite 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 because v3 needs to be configured, see http://blog.golang.org/laws-of-reflection

But basically, using reflection, we can cycle through the fields of the structure, see if it is updated to zero, and if so, we use the old value.

+5
source

Use reflection

You can update Model with reflection and 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 changed after calling this function.

Sample output

The "merger" of the following,

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

gives:

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

That is, it overwrites all the values โ€‹โ€‹present in the new structure in the old, ignoring the values โ€‹โ€‹that are "undefined".

Obviously, you can Marshal (or not) as you wish.

Full program

See the full working example on the Go Playground.

The usual caveats apply (check returned production errors!).

+2
source

Another option, if you do not want to use reflection, is to extract the object from the database and transfer its address to the JSON decoder. Only fields defined in the JSON of the update method will be updated. Then you can use the usual method to save the changes to the database. Here is 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, if you have {"ID":1,"name":"Do stuff","completed":false} in your database and send something like {"completed":true} your update method ( /todos/1 , where 1 is todoId ), you will get the following: {"ID":1,"name":"Do stuff","completed":true} .

+2
source

Source: https://habr.com/ru/post/988037/


All Articles