Avoid to save blank struct object in db - go

I am running the below code. When a user saved in db then blank address is saved I have assigned the null struct to address before save. I can not add the omitempty with all the fields for address struct. How I can avoid to save blank address object within users collection
package main
import (
"context"
"fmt"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type User struct {
Id int `json:"id" bson:"_id,omitempty"`
FirstName string `json:"first_name,omitempty" bson:"first_name,omitempty"`
LastName string `json:"last_name,omitempty" bson:"last_name,omitempty"`
FullName string `json:"full_name,omitempty" bson:"full_name,omitempty"`
CompanyName string `json:"company_name" bson:"company_name"`
Address AddressStruct `json:"address,omitempty" bson:"address,omitempty"`
}
type AddressStruct struct {
Address string `json:"address" bson:"address"`
City string `json:"city" bson:"city"`
State string `json:"state" bson:"state"`
Zipcode string `json:"zipcode" bson:"zipcode"`
Apt string `json:"apt" bson:"apt"`
Default bool `json:"default" bson:"default"`
Status int8 `json:"status,omitempty" bson:"status,omitempty"`
Country string `json:"country,omitempty" bson:"country,omitempty"`
ShortAddress string `json:"short_address" bson:"short_address"`
}
func main() {
var user User
user.FirstName = "Swati"
user.LastName = "Sharma"
user.FullName = "Swati Sharma"
user.Address = AddressStruct{}
client, ctx := ConnectDbWithNewDriver()
defer client.Disconnect(ctx)
a, b := DbInsertOne(client, user)
fmt.Println("Section1", a, b)
}
func ConnectDbWithNewDriver() (client *mongo.Client, ctx context.Context) {
ctx = context.Background()
client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://127.0.0.1:27017").SetConnectTimeout(5*time.Second))
if err != nil {
fmt.Println("CreateSession: ", err)
client.Disconnect(ctx)
return
}
return
}
func DbInsertOne(client *mongo.Client, data interface{}) (interface{}, error) {
collection := client.Database("swatitest").Collection("users")
insertResult, err := collection.InsertOne(context.TODO(), data)
if err != nil {
return nil, err
}
return insertResult.InsertedID, nil
}
When I run this code then record saved in db like this:
{
"_id" : ObjectId("61af41b32214b16fe93435a6"),
"first_name" : "Swati",
"last_name" : "Sharma",
"full_name" : "Swati Sharma",
"company_name" : "",
"address" : {
"address" : "",
"city" : "",
"state" : "",
"zipcode" : "",
"apt" : "",
"default" : false,
"short_address" : ""
}
}
I want to save like:
{
"_id" : ObjectId("61af41b32214b16fe93435a6"),
"first_name" : "Swati",
"last_name" : "Sharma",
"full_name" : "Swati Sharma",
"company_name" : ""
}

You may use the omitempty bson tag option, all Go drivers handle it and will not save the field if it's value is the zero value (for primitive types).
So add it to all fields you don't want to get saved as empty string:
type AddressStruct struct {
Address string `json:"address" bson:"address,omitempty"`
City string `json:"city" bson:"city,omitempty"`
State string `json:"state" bson:"state,omitempty"`
Zipcode string `json:"zipcode" bson:"zipcode,omitempty"`
Apt string `json:"apt" bson:"apt,omitempty"`
Default bool `json:"default" bson:"default,omitempty"`
Status int8 `json:"status,omitempty" bson:"status,omitempty"`
Country string `json:"country,omitempty" bson:"country,omitempty"`
ShortAddress string `json:"short_address" bson:"short_address,omitempty"`
}
If you can't modify the struct type, then don't save the struct value. Create your own type (where you add omitempty), copy the struct value into it, and save your own copy.

Related

Can't set up has-many association in GORM

I'm trying to set up an association between Users and PredictionsBags. My problem is that everything works OK if I use GORM's assumed names for referring objects, but I'd like to change the names a bit.
type User struct {
gorm.Model
// We’ll try not using usernames for now
Email string `gorm:"not null;unique_index"`
Password string `gorm:"-"`
PasswordHash string `gorm:"not null"`
Remember string `gorm:"-"` // A user’s remember token.
RememberHash string `gorm:"not null;unique_index"`
Bags []PredictionsBag
}
Every user, of course, owns zero or more PredictionsBags:
type PredictionsBag struct {
gorm.Model
UserID uint // I want this to be "OwnerID"
Title string
NotesPublic string `gorm:"not null"` // Markdown field. May be published.
NotesPrivate string `gorm:"not null"` // Markdown field. Only for (private) viewing and export.
Predictions []Prediction
}
And I'd like to have .Related() work in the usual way:
func (ug *userGorm) ByEmail(email string) (*User, error) {
var ret User
matchingEmail := ug.db.Where("email = ?", email)
err := first(matchingEmail, &ret)
if err != nil {
return nil, err
}
var bags []PredictionsBag
if err := ug.db.Model(&ret).Related(&bags).Error; err != nil {
return nil, err
}
ret.Bags = bags
return &ret, nil
}
My problem is that I can't find a way to change PredictionsBag.UserID to anything else and still have GORM figure out the relationships involved. I've been reading http://gorm.io/docs/has_many.html#Foreign-Key and if I change the relevant lines to
type User struct {
// …
Bags []PredictionsBag `gorm:"foreignkey:OwnerID"`
}
and
type PredictionsBag struct {
// …
OwnerID uint
// …
}
I get this error:
[2019-07-28 14:23:49] invalid association []
What am I doing wrong? I've also been reading http://gorm.io/docs/belongs_to.html, but I'm not sure which page to follow more closely.
I'll have to Check Related() when I get home, but I think what you're looking for is Preload() This is my example that works with what you want.
package main
import (
"errors"
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"log"
)
var DB *gorm.DB
func init() {
var err error
DB, err = gorm.Open("mysql", fmt.Sprintf("%s:%s#tcp(%s:3306)/%s?&parseTime=True&loc=Local", "root", "root", "localhost", "testing"))
if err != nil {
log.Fatal(err)
}
DB.DropTableIfExists(&User{}, &PredictionsBag{})
DB.AutoMigrate(&User{}, &PredictionsBag{})
user := User{Email:"dave#example.com"}
user.Bags = append(user.Bags, PredictionsBag{OwnerID: user.ID, NotesPrivate: "1", NotesPublic: "1"})
DB.Create(&user)
}
func main() {
user := User{Email:"dave#example.com"}
err := user.ByEmail()
if err != nil {
log.Println(err)
}
fmt.Println(user.ID, user.Email, "Bags:", len(user.Bags))
DB.Close()
}
type User struct {
gorm.Model
// We’ll try not using usernames for now
Email string `gorm:"not null;unique_index"`
Password string `gorm:"-"`
PasswordHash string `gorm:"not null"`
Remember string `gorm:"-"` // A user’s remember token.
RememberHash string `gorm:"not null;unique_index"`
Bags []PredictionsBag `gorm:"foreignkey:OwnerID"`
}
type PredictionsBag struct {
gorm.Model
OwnerID uint
Title string
NotesPublic string `gorm:"not null"` // Markdown field. May be published.
NotesPrivate string `gorm:"not null"` // Markdown field. Only for (private) viewing and export.
}
func (ug *User) ByEmail() error {
DB.Where("email = ?", ug.Email).Preload("Bags").Limit(1).Find(&ug)
if ug.ID == 0 {
return errors.New("no user found")
}
return nil
}
Using this might work with related, but I'm not sure what else needs to be changed:
Bags []PredictionsBag `gorm:"foreignkey:OwnerID;association_foreignkey:ID"`
Update:
I can get the Related() method to work, if you state the ForeignKey like the following:
DB.Where("email = ?", ug.Email).Limit(1).Find(&ug)
if ug.ID == 0 {
return errors.New("no user found")
}
if err := DB.Model(&ug).Related(&ug.Bags, "owner_id").Error; err != nil {
return err
}

parsing nested JSON with go

I am trying to parse a nested json on GO ,
the json looks like this:
{
"id" : 12345656,
"date" : "2018-05-02-18-16-17",
"lists" : [
{
"empoyee_id": "12343",
"name": "User1"
},
{
"contractor_id" : "12343",
"name": "User1"
},
{
"contractor_id" : "12343",
"name": "User1"
}
]
}
My struct
type Result struct {
id int64 `json:"id"`
Date string `json:"date"`
Lists []string `json:"lists"`
}
I am trying to access it using the following:
var result Result
json.Unmarshal(contents, &result)
How can I change the above to access to the employee_id or the contractor_id fields ?
You need to use another type to store the nested data rather than a slice of strings like so:
package main
import (
"fmt"
"encoding/json"
)
var contents string = `{
"id" : 12345656,
"date" : "2018-05-02-18-16-17",
"lists" : [
{
"empoyee_id": "12343",
"name": "User1"
},
{
"contractor_id" : "12343",
"name": "User1"
},
{
"contractor_id" : "12343",
"name": "User1"
}
]
}`
type Result struct {
ID int64 `json:"id"`
Date string `json:"date"`
Lists []Contractor `json:"lists"`
}
type Contractor struct {
ContractorID string `json:"contractor_id"`
EmployeeID string `json:"employee_id"`
Name string `json:"name"`
}
func main() {
var result Result
err := json.Unmarshal([]byte(contents), &result)
if err != nil {
panic(err)
}
fmt.Println(result)
}
Executable:
https://play.golang.org/p/7dYArgz1V8y
If you just want a single ID field on the nested object then you will need to do a custom unmarshal function on the result to work out which ID is present.

Marshall/Unmarshal JSONPB

I am trying to Unmarshal some json data to a proto message.
JSON
{
"id": 1,
"first_name": "name",
"phone_numbers": []
}
Proto
message Item {
uint32 id=1;
string name=2;
repeated string numbers=3;
}
Proto.go
type Item struct {
Id uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
Numbers []string `protobuf:"bytes,4,rep,name=numbers" json:"numbers,omitempty"`
}
How can I map the above JSON to my proto Message (from what I can see there is no way to specify tags in proto atm)?
Your JSON document doesn't match the proto definition; name != first_name and numbers != phone_numbers.
You can define another type that has the same fields as Item but different struct tags and then convert to Item:
var x struct {
Id uint32 `json:"id,omitempty"`
Name string `json:"first_name,omitempty"`
Numbers []string `json:"phone_numbers,omitempty"`
}
if err := json.Unmarshal(jsonDoc, &x); err != nil {
log.Fatal(err)
}
var i = Item(x)
If every JSON document you want to decode has this structure, it may be more convenient to let Item implement json.Unmarshaler:
package main
import (
"encoding/json"
"fmt"
"log"
)
var jsonDoc = []byte(`
{
"id": 1,
"first_name": "name",
"phone_numbers": [
"555"
]
}
`)
type Item struct {
Id uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
Numbers []string `protobuf:"bytes,4,rep,name=numbers" json:"numbers,omitempty"`
}
// You can define this function is item_json.go or so, then it
// isn't removed if you re-generate your types.
func (i *Item) UnmarshalJSON(b []byte) error {
type item struct {
Id uint32 `json:"id,omitempty"`
Name string `json:"first_name,omitempty"`
Numbers []string `json:"phone_numbers,omitempty"`
}
var x item
if err := json.Unmarshal(jsonDoc, &x); err != nil {
return err
}
*i = Item(x)
return nil
}
func main() {
var i Item
if err := json.Unmarshal(jsonDoc, &i); err != nil {
log.Fatal(err)
}
fmt.Printf("%#v\n", i)
}
Try it on the playground: https://play.golang.org/p/0qibavRJbwi

undefined (cannot refer to unexported field or method)

I'm trying to refer Users struct from the models package and trying to access the model from control.But I take following errors.
controllers/user.go:87: user.create_date undefined (cannot refer to unexported field or method create_date)
controllers/user.go:88: user.update_date undefined (cannot refer to unexported field or method update_date)
controllers/user.go:104: user.user_id undefined (cannot refer to unexported field or method user_id)
controllers/user.go:119: user.update_date undefined (cannot refer to unexported field or method update_date)
controllers/user.go:136: user.user_id undefined (cannot refer to unexported field or method user_id)
controllers/user.go:151: user.update_date undefined (cannot refer to unexported field or method update_date)
controllers/user.go:166: user.user_id undefined (cannot refer to unexported field or method user_id)
Models.go
package models
import(
"time"
)
type Users struct {
user_id int `json:"user_id" form:"user_id" gorm:"column:user_id"`
user_login string `json:"user_login" form:"user_login" gorm:"column:user_login"`
user_email string `json:"user_email" form:"user_email" gorm:"column:user_email"`
user_password string `json:"user_password" form:"user_password" gorm:"column:user_password"`
user_password_salt string `json:"user_password_salt" form:"user_password_salt" gorm:"column:user_password_salt"`
user_2factor_secret string `json:"user_2factor_secret" form:"user_2factor_secret" gorm:"column:user_2factor_secret"`
user_fullname string `json:"user_fullname" form:"user_fullname" gorm:"column:user_fullname"`
user_description string `json:"user_description" form:"user_description" gorm:"column:user_description"`
user_enabled string `json:"user_enabled" form:"user_enabled" gorm:"column:user_enabled"`
user_verified string `json:"user_verified" form:"user_verified" gorm:"column:user_verified"`
PublisherInfoID int `json:"PublisherInfoID" form:"PublisherInfoID" gorm:"column:PublisherInfoID"`
DemandCustomerInfoID int `json:"DemandCustomerInfoID" form:"DemandCustomerInfoID" gorm:"column:DemandCustomerInfoID"`
create_date time.Time `json:"create_date" gorm:"column:create_date"`
update_date time.Time `json:"update_date" gorm:"column:update_date"`
user_permission_cache string `json:"user_permission_cache" form:"user_permission_cache" gorm:"column:user_permission_cache"`
user_role int `json:"user_role" form:"user_role" gorm:"column:user_role"`
}
in controllers
package controllers
import (
"time"
"github.com/op/go-logging"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/go-sql-driver/mysql"
"../models"
)
var loguser = logging.MustGetLogger("AdsAPI")
type AdsControllerUser struct {
DB gorm.DB
}
func (ac *AdsControllerUser) SetDB(d gorm.DB) {
ac.DB = d
ac.DB.LogMode(true)
}
func (ac *AdsControllerUser) CreateUsers(c *gin.Context) {
var user models.Users
// This will infer what binder to use depending on the content-type header.
c.Bind(&user)
// Update Timestamps
user.create_date = time.Now()
user.update_date = time.Now()
err := ac.DB.Save(&user)
if err != nil {
loguser.Debugf("Error while creating a user, the error is '%v'", err)
res := gin.H{
"status": "403",
"error": "Unable to create user",
}
c.JSON(404, res)
return
}
content := gin.H{
"status": "201",
"result": "Success",
"UserID": user.user_id,
}
c.Writer.Header().Set("Content-Type", "application/json")
c.JSON(201, content)
}
func (ac *AdsControllerUser) UpdateUsers(c *gin.Context) {
// Grab id
id := c.Params.ByName("id")
var user models.Users
c.Bind(&user)
// Update Timestamps
user.update_date = time.Now()
//err := ac.DB.Model(&models.auth_User).Where("user_id = ?", id).Updates(&cm)
err := ac.DB.Where("user_id = ?", id).Updates(&user)
if err != nil {
loguser.Debugf("Error while updating a user, the error is '%v'", err)
res := gin.H{
"status": "403",
"error": "Unable to update user",
}
c.JSON(403, res)
return
}
content := gin.H{
"status": "201",
"result": "Success",
"UserID": user.user_id,
}
c.Writer.Header().Set("Content-Type", "application/json")
c.JSON(201, content)
}
func (ac *AdsControllerUser) DeleteUsers(c *gin.Context) {
// Grab id
id := c.Params.ByName("id")
var user models.Users
c.Bind(&user)
// Update Timestamps
user.update_date = time.Now()
err := ac.DB.Where("user_id = ?", id).Delete(&user)
if err != nil {
loguser.Debugf("Error while deleting a user, the error is '%v'", err)
res := gin.H{
"status": "403",
"error": "Unable to delete user",
}
c.JSON(403, res)
return
}
content := gin.H {
"result": "Success",
"UserID": user.user_id,
}
c.Writer.Header().Set("Content-Type", "application/json")
c.JSON(201, content)
}
Use Capitals for exported fields in struct, when referring struct in another package.
package models
import (
"time"
)
type Users struct {
ID int `json:"user_id" form:"user_id" gorm:"column:user_id"`
Login string `json:"user_login" form:"user_login" gorm:"column:user_login"`
Email string `json:"user_email" form:"user_email" gorm:"column:user_email"`
Password string `json:"user_password" form:"user_password" gorm:"column:user_password"`
PasswordSalt string `json:"user_password_salt" form:"user_password_salt" gorm:"column:user_password_salt"`
TwoFactorSecret string `json:"user_2factor_secret" form:"user_2factor_secret" gorm:"column:user_2factor_secret"`
Fullname string `json:"user_fullname" form:"user_fullname" gorm:"column:user_fullname"`
Description string `json:"user_description" form:"user_description" gorm:"column:user_description"`
Enabled string `json:"user_enabled" form:"user_enabled" gorm:"column:user_enabled"`
Verified string `json:"user_verified" form:"user_verified" gorm:"column:user_verified"`
PublisherInfoID int `json:"PublisherInfoID" form:"PublisherInfoID" gorm:"column:PublisherInfoID"`
DemandCustomerInfoID int `json:"DemandCustomerInfoID" form:"DemandCustomerInfoID" gorm:"column:DemandCustomerInfoID"`
CreateDate time.Time `json:"create_date" gorm:"column:create_date"`
UpdateDate time.Time `json:"update_date" gorm:"column:update_date"`
PermissionCache string `json:"user_permission_cache" form:"user_permission_cache" gorm:"column:user_permission_cache"`
Role int `json:"user_role" form:"user_role" gorm:"column:user_role"`
}
Now do Users.ID to get fields.

Query Document with different struct for results

I have a collection of documents that were inserted into Mongo looking something like this:
type Stats struct {
UserStatus string `json:"userStatus" bson:"userStatus"`
... a bunch more fields
}
type User struct {
ID bson.ObjectId `json:"-" bson:"_id"`
LastName string `json:"lastName" bson:"lastName"`
FirstName string `json:"firstName" bson:"firstName"`
Role string `json:"role" bson:"role"`
Tags []string `json:"tags" bson:"tags"`
... (a bunch more fields)
Stats UserStats `json:"stats" bson:"stats"`
}
I want to query it to get a specific report, so I tried this:
func UserNameReport() {
... get mongo session, etc.
// create struct of just the data I want returned
type UserNames struct {
LastName string `json:"lastName" bson:"lastName"`
FirstName string `json:"firstName" bson:"firstName"`
... etc
UserStats Stats `json:"stats" bson:"stats"`
}
projection := bson.M{"lastName":1, "firstName":1, etc}
result := []UserNames{}
err := x.Find({query user collection}).Select(projection).All(&result)
...
}
This works - my question is, how can I include just ONE field from the 'Stats' struct? In other words,
I essentially want the "projection" to be this:
projection := bson.M{"lastName":1, ..., "stats.userStatus":1} <-- stats.userStatus doesn't work
...
err := x.Find({query user collection}).Select(projection).All(&result)
I get the entire "Stats" embedded struct in the results - how can I filter out just one field from the sub-document in and put it into the result set?
Thanks!
It works perfectly for me, with MongoDB 2.6.5
The following code:
package main
import (
"fmt"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"log"
)
type Statistics struct {
Url string
Hits int
}
type Person struct {
Num int
Uuid string
Name string
Stats []Statistics
}
func main() {
// Connect to the database
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()
// Remove people collection if any
c := session.DB("test").C("people")
c.DropCollection()
// Add some data
err = c.Insert(
&Person{1, "UUID1", "Joe", []Statistics{Statistics{"a", 1}, Statistics{"b", 2}}},
&Person{2, "UUID2", "Jane", []Statistics{Statistics{"c", 3}, Statistics{"d", 4}}},
&Person{3, "UUID3", "Didier", []Statistics{Statistics{"e", 5}, Statistics{"f", 6}}})
if err != nil {
log.Fatal(err)
}
result := []Person{}
err = c.Find(bson.M{"$or": []bson.M{bson.M{"uuid": "UUID3"}, bson.M{"name": "Joe"}}}).Select(bson.M{"num": 1, "name": 1, "stats.hits": 1}).All(&result)
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
results in:
[{1 Joe [{ 1} { 2}]} {3 Didier [{ 5} { 6}]}]
... which is precisely what I would expect.
Maybe this will help others - essentially I was trying to take a document with an embedded document and return a result set like I would do in SQL with a select a.LastName + ', ' + a.FirstName as Name, b.OtherData and in essence have a different 'table' / 'document'.
So here is my current solution - love to get better ones (more performant?) though!
I created a new struct and I'm using the 'mapstructure' library
import "github.com/goinggo/mapstructure"
type Stats struct {
UserStatus string `json:"userStatus" bson:"userStatus"`
... a bunch more fields
}
type User struct {
ID bson.ObjectId `json:"-" bson:"_id"`
LastName string `json:"lastName" bson:"lastName"`
FirstName string `json:"firstName" bson:"firstName"`
Role string `json:"role" bson:"role"`
Tags []string `json:"tags" bson:"tags"`
... (a bunch more fields)
Stats UserStats `json:"stats" bson:"stats"`
}
type MyReportItem struct {
FirstName string `json:"firstName" jpath:"firstName"`
LastName string `json:"lastName" jpath:"lastName"`
Status string `json:"status" jpath:"stats.userStatus"`
}
type MyReport struct {
Results []MyReportItem `json:"results"`
}
func xxxx(w http.ResponseWriter, r *http.Request) {
var users MyReportItem
// the results will come back as a slice of map[string]interface{}
mgoResult := []map[string]interface{}{}
// execute the query
err := c.Find(finder).Select(projection).All(&mgoResult)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
user := MyReportItem{}
// iterate through the results and decode them into the MyReport struct
for _, x := range mgoResult {
docScript, _ := json.Marshal(x)
docMap := map[string]interface{}{}
json.Unmarshal(docScript, &docMap)
err := mapstructure.DecodePath(docMap, &user)
if err == nil {
users.Results = append(users.Results, user)
}
}
... send back the results ...
_ := json.NewEncoder(w).Encode(&users)
}
Now I get a slice of objects in the form:
results: [
{
firstName: "John",
lastName: "Doe",
status: "active"
}
...
]
Instead of:
{
firstName: "John",
lastName: "Doe",
stats: {
status: "active"
}
}

Resources