Golang Selecting fields from a struct array - mongodb

I get an array of all the users with an attribute ID in their document:
Users := []backend.User{}
err := Collection.Find(bson.M{"channel_id": bson.ObjectIdHex(chId)}).All(&Users)
if err != nil {
println(err)
}
Which I want to send as a JSON response back to the browser/client. However, the User struct contains things like IDs and Hahsed Passwords which i don't want to send back!
I was looking at something like using the reflect package to select the fields of the struct and then putting them into a map[string]interface{} but im not sure how to do it with an array of users.

You can ignore struct fields while json.Marshal.
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Id int `json:"-"`
Name string `json:"name"`
}
type Users []*User
func main() {
user := &Users{
&User{1, "Max"},
&User{2, "Alice"},
&User{3, "Dan"},
}
json, _ := json.Marshal(user)
fmt.Println(string(json))
}
Runnable example in Play Golang: http://play.golang.org/p/AEC_TyXE3B
There is a very useful part about using the tags in the doc. Same for XML, but it's more complicated for obvious reasons.

Related

Loop over collections in mongodb and decode to various go structs

I have a series of collections in a mongodb instance where I want to pull all the collections and decode all documents in the collections so I can mutate them as I go.
I am using go to list the collection names:
...
collections, err := db.ListCollectionNames(context.TODO(), bson.M{})
if err != nil {
log.Fatalf("Failed to get coll names: %v", err)
}
for _, coll := range collections {
collection := db.Collection(coll)
...
This works flawlessly and the next step would be to open a cursor to each collection and loop over the cursor and call decode on each object to its corresponding type.
I have defined all the types in a types package:
type Announcements struct {
Id string `bson:"_id"`
Content string `bson:"content"`
Title string `bson:"title"`
DateModified time.Time `bson:"dateModified,omitempty"`
ModifiedBy string `bson:"modifiedBy,omitempty"`
}
The issue is that I cant define the variable to be used for decoding dynamically.
This code would need to be repeated for every single collection
var obj Announcements // This would need to be dynamic
for cursor.Next(context.Background()) {
err := cursor.Decode(&obj)
if err != nil {
log.Fatal(err)
}
log.Printf("%#v\n", obj)
}
Is there a way to do this without repeating this many times? I realize a dynamically typed language would be better for this. Wanted to ask before I migrate the work to a scripting language.
I have tried using the reflect package and switch statements to instantiate the variable dynamnically and using the interface{} type but that force the decode to use bson types and not my defined structs.
-- edit --
I have tried to use a map to link collection names to types but to no avail.
var Collections = map[string]interface{}{
"announcements": Announcements{},
"badges": Badges{},
"betaUser": BetaUser{},
"bonusLearningItems": BonusLearningItems{},
"books": Books{},
...
}
So that when I use the obj var I tried an assignment like:
var obj types.Collections[coll]
hoping that would give me a variable of type Announcement if I was had looped to the announcements collection. but when I call decode it returns bson types.
I need to dynamically define the obj variable type.
There are many ways you can do this. One of them is by using a callback function:
func Process(cursor *mongo.Cursor, cb func(), out interface{}) {
for cursor.Next(context.Background()) {
err := cursor.Decode(out)
if err != nil {
log.Fatal(err)
}
cb()
log.Printf("%#v\n", obj)
}
}
Then define a map:
type collHandler struct {
New func() interface{}
Process func(interface{})
}
var collections=map[string]func() interface{} {
"announcements": collHandler{
New: func() interface {} {return &Announcement{}},
Process: func(data interface{}) {
processAnnouncements(data.(*Announcement))
},
},
...
}
And then you can:
handler:=collections[collectionName]
obj:=handler.New()
process(cursor,func() {
handler.Process(obj)
},
&obj)
This way, you can place the iteration into a common function, and use closures to deal with type specific logic.

How to work with has many relation in gorm?

import (
"gorm.io/gorm"
"gorm.io/driver/postgres"
)
type School struct {
gorm.Model
Students []Student `json:"students"`
}
type Student struct {
gorm.Model
Name string `json:"name"`
}
func init() {
//connect to db first
conn, err := gorm.Open(postgres.New(postgres.Config{
DSN: dbUri,
PreferSimpleProtocol: true,
}), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db = conn
db.AutoMigrate(&Student{}, &School{})
}
Create the structs and automigrating it gives me an error. Do you know why this is? Also how to you work with has many relation in gorm, what kind of data does it create in postgres?
Error -
Need to define a valid foreign key for relations or it need to implement the Valuer/Scanner interface
You need to add a SchoolID field to your Student. See the docs here for full usage.
type Student struct {
gorm.Model
SchoolID uint
Name string `json:"name"`
}
To answer the second part, it will create two tables for you. Schools and students. The students will have a foreign key that points to the ID of a school. I would read the docs to learn how this works more.

Using an opaque ID type in place of primitive.ObjectID

In a db package, I don't want to expose MongoDB's primitive.ObjectID type as part of its public API. Instead, I want to define type Id interface{} within the db package and expose that. Since the driver doesn't know how to transform the database's ObjectID type into my custom ID type, I registered a type decoder like so:
type Id interface{}
var idType = reflect.TypeOf((*Id)(nil)).Elem()
type User struct {
Id `bson:"_id,omitempty"`
}
func main() {
reg := bson.NewRegistryBuilder().
RegisterTypeDecoder(idType, bsoncodec.ValueDecoderFunc(idDecodeValue)).
Build()
client, err := mongo.Connect(context.TODO(), options.Client().
ApplyURI("...").
SetRegistry(reg))
coll := client.Database("...").Collection("...")
var u0 User
coll.InsertOne(context.TODO(), u0)
var u1 User
coll.FindOne(context.TODO(), bson.D{}).Decode(&u1)
}
func idDecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.IsValid() || val.Type() != idType {
return bsoncodec.ValueDecoderError{
Name: "IdDecodeValue",
Types: []reflect.Type{idType},
Received: val,
}
}
oid, _ := vr.ReadObjectID()
val.Set(reflect.ValueOf(oid))
return nil
}
I saw that there exists a bsoncodec.NewEmptyInterfaceCodec() to use in place of idDecodeValue, but that only seems to work when User.Id's type is exactly interface{}. I wrote idDecodeValue based off of the existing codecs, but I'm not entirely sure what's going on (mostly due to not knowing when val.IsValid() would ever return false). Is all of this the best, most idiomatic way to go about supporting a custom ID type?

Go compile error "undefined function"

I have the following pieces of code:
Interface & function definition:
package helper
import "gopkg.in/mgo.v2/bson"
// Defines an interface for identifiable objects
type Identifiable interface {
GetIdentifier() bson.ObjectId
}
// Checks if a slice contains a given object with a given bson.ObjectId
func IsInSlice(id bson.ObjectId, objects []Identifiable) bool {
for _, single := range objects {
if single.GetIdentifier() == id {
return true
}
}
return false
}
The definition of the user struct which satisfies 'Identifiable':
package models
import (
"github.com/my/project/services"
"gopkg.in/mgo.v2/bson"
)
type User struct {
Id bson.ObjectId `json:"_id,omitempty" bson:"_id,omitempty"`
Username string `json:"username" bson:"username"`
Email string `json:"email" bson:"email"`
Password string `json:"password" bson:"password"`
Albums []bson.ObjectId `json:"albums,omitempty" bson:"albums,omitempty"`
Friends []bson.ObjectId `json:"friends,omitempty" bson:"friends,omitempty"`
}
func GetUserById(id string) (err error, user *User) {
dbConnection, dbCollection := services.GetDbConnection("users")
defer dbConnection.Close()
err = dbCollection.Find(bson.M{"_id": bson.ObjectIdHex(id)}).One(&user)
return err, user
}
func (u *User) GetIdentifier() bson.ObjectId {
return u.Id
}
A test which checks for the existence of an object inside a slice:
package controllers
import (
"github.com/my/project/helper"
"gopkg.in/mgo.v2/bson"
)
var testerId = bson.NewObjectId()
var users = []models.Users{}
/* Some code to get users from db */
if !helper.IsInSlice(testerId, users) {
t.Fatalf("User is not saved in the database")
}
When I try to compile the test it I get the error: undefined helper.IsInSlice. When I rewrite the IsInSlice method to not take []Identifiable but []models.User it works fine.
Any ideas?
Your problem is that you're trying to use a value of type []models.Users{} as a value of type []Identifiable. While models.Users implements Identifiable, Go's type system is designed so that slices of values implementing an interface cannot be used as (or converted to) slices of the interface type.
See the Go specification's section on conversions for more details.
Apparently, Go did not rebuild my package and was looking for the function in an old build. It was therefore undefined. Performing rm -fr [GO-ROOT]/pkg/github.com/my/project/models did the trick.

Go XML Marshalling and the Root Element

In Go, you can marshall a struct to XML, e.g.:
package main
import (
"encoding/xml"
"fmt"
)
type person struct {
Name string
Starsign string
}
func main() {
p := &person{"John Smith", "Capricorn"}
b,_ := xml.MarshalIndent(p,""," ")
fmt.Println(string(b))
}
produces output:
<person>
<Name>John Smith</Name>
<Starsign>Capricorn</Starsign>
</person>
My problem is, the person type is lower-case "p" because I want that to be private to the package. But I'd prefer the XML element to be uppercase: <Person>. The fields within the struct can be marshalled to other names using tags (e.g. `xml:"name"`) against the structure fields but this doesn't seem to be an option for the structure type.
I have a work-around using templates, but it would be nice to know a better answer.
According to the encoding/xml.Marshal documentation:
The name for the XML elements is taken from, in order of preference:
the tag on the XMLName field, if the data is a struct
the value of the XMLName field of type xml.Name
the tag of the struct field used to obtain the data
the name of the struct field used to obtain the data
the name of the marshalled type
You can use a tag on the XMLName field in the struct to override the person struct's XML tag name. In order to avoid putting it in your actual person struct, you can create an anonymous struct that embeds the person struct you are marshaling.
package main
import (
"encoding/xml"
"fmt"
)
type person struct {
Name string
Starsign string
}
func marshalPerson(p person) ([]byte, error) {
tmp := struct {
person
XMLName struct{} `xml:"Person"`
}{person: p}
return xml.MarshalIndent(tmp, "", " ")
}
func main() {
p := person{"John Smith", "Capricorn"}
b, _ := marshalPerson(p)
fmt.Println(string(b))
}
This also works, though I don't think it's particularly pretty.
However, this worked in a lot more straight forward manner for me than the other accepted solution from 5 years ago.
package main
import (
"encoding/xml"
"fmt"
)
type person struct {
XMLName xml.Name
Name string
Starsign string
}
func main() {
p := &person{xml.Name{Local: "Person"}, "John Smith", "Capricorn"}
b,_ := xml.MarshalIndent(p,""," ")
fmt.Println(string(b))
}
I think the easiest thing is just to add a dummy field to the person struct with the XML tag.
A struct{} element does not use any storage, I checked with unsafe.Sizeof().
package main
import (
"encoding/xml"
"fmt"
)
type person struct {
Name string
Starsign string
XMLName struct{} `xml:"Person"`
}
func main() {
p := &person{Name: "John Smith", Starsign: "Capricorn"}
b, _ := xml.MarshalIndent(p, "", " ")
fmt.Println(string(b))
}
go playground
If you prefer to initialize the struct without using field names, it is necessary to add an item to initialize the empty struct, like this:
p := &person{"John Smith", "Capricorn", struct{}{}}