We are using Golang to implement a REST API which including CRUD, in Update service, client could send partial JSON including changed fields, and we need to handle for updating entity with these changes.
Logically, we need to get entity by Id from DB to struct, and then unmarshal payload json to another struct and update entity.
However if payload json is not fully, for example I have struct
type Customer struct {
Id int64 `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
And JSON request looks like
{
"Name": "Updated name"
}
Then the customer should be updated with new name.
That's simple example, actually it could be a nested struct and nested json, how could we handle that case with golang, or event other language like Java, .NET
If the Update request uses the same Customer
struct then the struct fields could be pointers to differentiate between zero value and value not being set in the JSON.
Now all you need to do is merge existing struct into updated Consumer
struct.
For this you can use https://github.com/imdario/mergo library in Go.
package main
import (
"fmt"
"github.com/imdario/mergo"
"encoding/json"
"os"
)
type Address struct {
City string `json:"city"`
}
type Customer struct {
Id int64 `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
Address *Address `json:"address"`
}
func main() {
old1 := &Customer{Id:1, Name:"alpha", Age:5, Address:&Address{City:"Delhi"}}
b := []byte(`{"name": "beta"}`) //no address, age specified picks from old
up1 := new(Customer)
json.Unmarshal(b, up1)
if err := mergo.Merge(up1, old1); err != nil {
fmt.Printf("err in 1st merge: %v
", err)
os.Exit(1)
}
m1, _ := json.Marshal(up1)
fmt.Printf("merged to: %v
", string(m1))
old2 := &Customer{Id:1, Name:"alpha", Age:5, Address:&Address{City:"Delhi"}}
b2 := []byte(`{ "address": {"city": "mumbai"}}`) //address specified
up2 := new(Customer)
json.Unmarshal(b2, up2)
if err := mergo.Merge(up2, old2); err != nil {
fmt.Printf("err in 1st merge: %v
", err)
os.Exit(1)
}
m2, _ := json.Marshal(up2)
fmt.Printf("merged to: %v
", string(m2))
}
from your comments it seems you are hitting the zero-value issue a lot of users with go
encounter i.e. how does one tell if the input data passed a legitimate value - or was that value zeroed by default omission.
The only way to solve this is to use pointers. So in your example, changing your data struct to this:
type Customer struct {
Id *int64 `json:"id"`
Name *string `json:"name"`
Age *int `json:"age"`
}
Then after unmarshaling, any uninitialized fields will have nil
values e.g.
var c Customer
err := json.Unmarshal(jsonData, &c)
if err != nil {
panic(err)
}
if c.Id != nil {
log.Println("TODO: added SQL update parms for c.Id:", *c.Id)
}
if c.Name != nil {
log.Println("TODO: added SQL update params for c.Name:", *c.Name)
}
if c.Age != nil {
log.Println("TODO: added SQL update parms for c.Age:", *c.Age)
}
Note: care must be taken to ensure one does not accidentally reference any nil pointers which would trigger an instant panic
.
Working playground example.