One method to handle all the struct types that embed one common struct (json marshalling)

我们两清 提交于 2019-12-24 06:47:13

问题


I have a gin-gonic web app with somewhat MVC architecture. I created a couple of models, all of them embed one common struct:

type User struct {
   ID int
   Name string
}

type Admin struct {
   User
   Level int
}

... {
   User
}

Now I want to store them in database in json format. The goal I want to accomplish is to code only one function/method that will marshal any model and will save save it into DB. This method must marshal all the fields of current model, not only from User struct, e.g. User must be marshaled into {id: 1, name: "zhora"}, while Admin will go into {id: 1, name: "gena", level: 2}.

Like this one:

func (i *User) Save() {
  data, err := json.Marshal(i)
  check(err)
  if i.ID == 0 {
    _, err = app.DB.Exec(`INSERT INTO users(data) VALUES ($1) `, string(data))
  } else {
    _, err = app.DB.Exec(`UPDATE users SET data = $1 WHERE id=$2`, string(data), i.ID)
  }
  check(err)
}

Right now I have to copy/paste this func to every model file, changing only method receiver. Can this be avoided?


回答1:


You may use one func Save(d interface{}) like this:

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID   int
    Name string
}

type Admin struct {
    User
    Level int
}

func main() {
    Save(User{})
    Save(Admin{})
}

func Save(d interface{}) {
    body, err := json.Marshal(d)
    if err != nil {
        panic(err)
    }
    st := string(body)
    fmt.Println(st)
}

output:

{"ID":0,"Name":""}
{"ID":0,"Name":"","Level":0}

for your case, use this one function for all types:

func Save(i interface{}, id int) {
    data, err := json.Marshal(i)
    check(err)
    if id == 0 {
        _, err = app.DB.Exec(`INSERT INTO users(data) VALUES ($1) `, string(data))
    } else {
        _, err = app.DB.Exec(`UPDATE users SET data = $1 WHERE id=$2`, string(data), id)
    }
    check(err)
}

And call it like this:

u := User{}
a := Admin{}

Save(u, u.ID)
Save(a, a.ID)

And Yes, this is even simplifies call to Save to one parameter:

package main

import (
    "encoding/json"
    "fmt"
)

type Model interface {
    ID() int
    setID(int)
}

type User struct {
    Id   int
    Name string
}

func (t User) ID() int      { return t.Id }
func (t User) setID(id int) { t.Id = id }

type Admin struct {
    User
    Level int
}

func main() {
    Save(User{})
    Save(Admin{})
}

func Save(d Model) {
    body, err := json.Marshal(d)
    if err != nil {
        panic(err)
    }
    st := string(body)
    fmt.Println(st)

    fmt.Println("ID is ", d.ID())
}

output:

{"Id":0,"Name":""}
ID is  0
{"Id":0,"Name":"","Level":0}
ID is  0

Now You may use this one function for all types:

func Save(i Model) {
    data, err := json.Marshal(i)
    check(err)
    id := i.ID()
    if id == 0 {
        _, err = app.DB.Exec(`INSERT INTO users(data) VALUES ($1) `, string(data))
    } else {
        _, err = app.DB.Exec(`UPDATE users SET data = $1 WHERE id=$2`, string(data), id)
    }
    check(err)
}

And call it like this:

u := User{}
a := Admin{}

Save(u)
Save(a)

Effective Go:

Getters

Go doesn't provide automatic support for getters and setters. There's nothing wrong with providing getters and setters yourself, and it's often appropriate to do so, but it's neither idiomatic nor necessary to put Get into the getter's name. If you have a field called owner (lower case, unexported), the getter method should be called Owner (upper case, exported), not GetOwner. The use of upper-case names for export provides the hook to discriminate the field from the method. A setter function, if needed, will likely be called SetOwner. Both names read well in practice:

owner := obj.Owner()
if owner != user {
    obj.SetOwner(user)
}


来源:https://stackoverflow.com/questions/39131886/one-method-to-handle-all-the-struct-types-that-embed-one-common-struct-json-mar

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!