I have a BaseEntity struct and one method with *BaseEntity receiver that parses xml from file
type BaseEntity struct {
FilePath string
Collection string
}
func (this *BaseEntity) Parse() *[]byte {
xmlFile, err := ioutil.ReadFile(this.FilePath)
if err != nil {
fmt.Println("Error opening file:", err)
return nil
}
return &xmlFile
}
I also have several structs where BaseEntity is embedded, for eg. Users, Posts, Comments, Tags ... etc
type Users struct {
Data []User `xml:"row"`
BaseEntity
}
type User struct {
WebsiteUrl string `xml:"WebsiteUrl,attr"`
UpVotes string `xml:"UpVotes,attr"`
Id string `xml:"Id,attr"`
Age string `xml:"Age,attr"`
DisplayName string `xml:"DisplayName,attr"`
AboutMe string `xml:"AboutMe,attr"`
Reputation string `xml:"Reputation,attr"`
LastAccessDate string `xml:"LastAccessDate,attr"`
DownVotes string `xml:"DownVotes,attr"`
AccountId string `xml:"AccountId,attr"`
Location string `xml:"Location,attr"`
Views string `xml:"Views,attr"`
CreationDate string `xml:"CreationDate,attr"`
ProfileImageUrl string `xml:"ProfileImageUrl,attr"`
}
func (this *Users) LoadDataToDB() {
file := this.Parse()
xml.Unmarshal(*file, &this)
// Init db
db := database.MgoDb{}
db.Init()
defer db.Close()
for _, row := range this.Data {
err := db.C(this.Collection).Insert(&row)
if err != nil {
fmt.Println(err)
}
}
}
And for each of these structs I need to create a method LoadDataToDB that loads data in db and has some code.
This method can't have *BaseEntity receiver because I can't get this.Data in base struct and I can't get this struct from parameters because it always a different struct.
How to DRY and move this repeating method from every embedded struct to one?
Related
I'm new to Golang and Gin framework, I have created two models
type Product struct {
gorm.Model
Name string
Media []Media
}
type Media struct {
gorm.Model
URI string
ProductID uint
}
and I send a POST request to save a new product, the body was:
{
"name": "Product1",
"media": [
"https://server.com/image1",
"https://server.com/image2",
"https://server.com/image3",
"https://server.com/video1",
"https://server.com/video2"
]
}
And I save a new product using this code
product := Product{}
if err := context.ShouldBindJSON(product); err != nil { // <-- here the error
context.String(http.StatusBadRequest, fmt.Sprintf("err: %s", err.Error()))
return
}
tx := DB.Create(&product)
if tx.Error != nil {
context.String(http.StatusBadRequest, fmt.Sprintf("err: %s", tx.Error))
return
}
the return error message is
err: json: cannot unmarshal string into Go struct field Product.Media of type models.Media
I know that ShouldBindJSON can't convert media-string to media-object, but what is the best practice to do this?
Your payload doesn't match the model. In the JSON body, media is an array of strings, whereas in the model it's a struct with two fields and the embedded gorm model.
If you can't change anything of your current setup, implement UnmarshalJSON on Media and set the URI field from the raw bytes. In the same method you may also initialize ProductID to something (if needed).
func (m *Media) UnmarshalJSON(b []byte) error {
m.URI = string(b)
return nil
}
Then the binding will work as expected:
product := Product{}
// pass a pointer to product
if err := context.ShouldBindJSON(&product); err != nil {
// handle err ...
return
}
fmt.Println(product) // {Product1 [{"https://server.com/image1" 0} ... }
I've completed the Go course but I cannot think as to why this function isn't working and I am just guessing at this point
Error message:
./form.go:34:2: too many arguments to return
have (string)
want ()
func main() {
name()
}
func name() {
nameapi, err := http.Get("https://randomuser.me/api/")
if err != nil {
log.Fatal(err)
}
nameapiData, err := ioutil.ReadAll(nameapi.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(nameapiData)) // If not in string, byte form
var responseObject Response
json.Unmarshal(nameapiData, &responseObject)
fmt.Println()
fmt.Println()
fmt.Println()
fmt.Println(responseObject.Results[0].Name.First)
//flname := responseObject.Results[0].Name.First + " " + response.Object.Results[0].Name.Last
returnvalue := responseObject.Results[0].Name.First
return returnvalue
}
type Response struct {
Results []struct {
Gender string `json:"gender"`
Name struct {
Title string `json:"title"`
First string `json:"first"`
Last string `json:"last"`
} `json:"name"`
}
}
Edited to add struct.
Your function is declared as:
func name()
This means that it takes no arguments and returns nothing.
However, you are attempting to return returnvalue which is probably a string given the error.
Either use a naked return or specify that the function must return a string:
func name() string
I recommend you take the tour of Go, this is covered fairly early on.
I'm trying to write an API that will do different things with the data depending on its purpose.
API results - The API should expose certain fields to the user
Validation - Validation should handle different schemas e.g. login form doesn't require Name, but register does
Database - The database should save everything, including password etc. - if I try using the same struct for the API and DB with json:"-" or un-exporting the field the database save also ignores the field
Some sample code is below with a couple of comments in capitals to show where I ideally need to type cast to change the data. As they are different structs, they cannot be type cast, so I get an error. How can I fix this?
Alternatively, what is a better way of doing different things with the data without having lots of similar structs?
// IDValidation for uuids
type IDValidation struct {
ID string `json:"id" validate:"required,uuid"`
}
// RegisterValidation for register form
type RegisterValidation struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8"`
}
// UserModel to save in DB
type UserModel struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password,omitempty"`
Active bool `json:"active,omitempty"`
CreatedAt int64 `json:"created_at,omitempty"`
UpdatedAt int64 `json:"updated_at,omitempty"`
jwt.StandardClaims
}
// UserAPI data to display to user
type UserAPI struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Email string `json:"email"`
Active bool `json:"active,omitempty"`
}
// register a user
func register(c echo.Context) error {
u := new(UserModel)
if err := c.Bind(u); err != nil {
return err
}
// NEED TO CAST TO RegisterValidation HERE?
if err := c.Validate(u); err != nil {
return err
}
token, err := u.Register()
if err != nil {
return err
}
return c.JSON(http.StatusOK, lib.JSON{"token": token})
}
// retrieve a user
func retrieve(c echo.Context) error {
u := IDValidation{
ID: c.Param("id"),
}
if err := c.Validate(u); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
// NEED TO CAST USER TO UserAPI?
user, err := userModel.GetByID(u.ID)
if err != nil {
return err
}
return c.JSON(200, lib.JSON{"message": "User found", "user": user})
}
I've got some REST API with my models defined as Go structs.
type User struct {
FirstName string
LastName string
}
Then I've got my database methods for getting data.
GetUserByID(id int) (*User, error)
Now I'd like to replace my REST API with https://github.com/twitchtv/twirp .
Therefore I started defining my models inside .proto files.
message User {
string first_name = 2;
string last_name = 3;
}
Now I've got two User types. Let's call them the native and the proto type.
I've also got a service defined in my .proto file which returns a user to the frontend.
service Users {
rpc GetUser(Id) returns (User);
}
This generates an interface that I have to fill in.
func (s *Server) GetUser(context.Context, id) (*User, error) {
// i'd like to reuse my existing database methods
u, err := db.GetUserByID(id)
// handle error
// do more stuff
return u, nil
}
Unfortunately this does not work. My database returns a native User but the interface requires a proto user.
Is there an easy way to make it work? Maybe using type aliases?
Thanks a lot!
One way you can solve your problem is by doing the conversion manually.
type User struct {
FirstName string
LastName string
}
type protoUser struct {
firstName string
lastName string
}
func main() {
u := db() // Retrieve a user from a mocked db
fmt.Println("Before:")
fmt.Printf("%#v\n", *u) // What db returns (*protoUser)
fmt.Println("After:")
fmt.Printf("%#v\n", u.AsUser()) // What conversion returns (User)
}
// Mocked db that returns pointer to protoUser
func db() *protoUser {
pu := protoUser{"John", "Dough"}
return &pu
}
// Conversion method (converts protoUser into a User)
func (pu *protoUser) AsUser() User {
return User{pu.firstName, pu.lastName}
}
The key part is the AsUser method on the protoUser struct.
There we simply write our custom logic for converting a protoUser into a User type we want to be working with.
Working Example
As #Peter mentioned in the comment section.
I've seen a project which made it with a custom Convert function. It converts the Protobuf to local struct via json.Unmarshal, not sure how's the performance but it's a way to go.
Preview Code PLAYGROUND
// Convert converts the in struct to out struct via `json.Unmarshal`
func Convert(in interface{}, out interface{}) error {
j, err := json.Marshal(in)
if err != nil {
return err
}
err = json.Unmarshal(j, &out)
if err != nil {
return err
}
return nil
}
func main() {
// Converts the protobuf struct to local struct via json.Unmarshal
var localUser User
if err := convert(protoUser, &localUser); err != nil {
panic(err)
}
}
Output
Before:
main.ProtoUser{FirstName:"John", LastName:"Dough"}
After:
main.User{FirstName:"John", LastName:"Dough"}
Program exited.
// UserInfo 用来解构返回的数据
type UserInfo struct {
gender string `dynamo:"gender"`
product string `dynamo:"product"`
id string `dynamo:"id"`
createTime int `dynamo:"create_time"`
name string `dynamo:"name"`
}
// GetUserInfoByID 根据userId在supe_user表取回用户信息
func GetUserInfoByID(userId string) (UserInfo, error) {
queryInput := dynamodb.GetItemInput{
Key: map[string]*dynamodb.AttributeValue{
"userId": {
S: aws.String(userId),
},
},
TableName: aws.String("user"),
}
result, err := dbsession.DynamoDB.GetItem(&queryInput)
userInfo := UserInfo{}
if err != nil {
fmt.Println(err.Error())
return userInfo, err
}
unmarshalMapErr := dynamodbattribute.UnmarshalMap(result.Item, &userInfo)
if unmarshalMapErr != nil {
return userInfo, err
}
fmt.Println(result.Item)
fmt.Println(userInfo.name)
return userInfo, nil
}
Why is this not working? It did not throw any error, just not working...
My guess is something wrong with my UserInfo type, but can't figure the right way to do this, help, please.
In Go, a name is exported if it begins with a capital letter. You should make first letters of fields UPPERCASED to make sure they're exported, like:
type UserInfo struct {
Gender string `dynamo:"gender"`
Product string `dynamo:"product"`
Id string `dynamo:"id"`
CreateTime int `dynamo:"create_time"`
Name string `dynamo:"name"`
}
more info: https://www.goinggo.net/2014/03/exportedunexported-identifiers-in-go.html