Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating objects from REST API calls - struct merge?

Tags:

go

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

like image 754
Jon Molnar Avatar asked May 27 '15 18:05

Jon Molnar


People also ask

How do I update data on REST API?

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.

Is it okay to use POST FOR REST API updates?

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.

What is update method in REST API?

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.


2 Answers

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

like image 43
Thiago Zanivan Felisberto Avatar answered Oct 15 '22 10:10

Thiago Zanivan Felisberto


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.

like image 94
dave Avatar answered Oct 15 '22 11:10

dave