I have this data schema:
"person": {
"name": "John",
"pets": [
{
"name": "Birdie"
}
]
}
This is the struct to insert the Person document to MongoDB:
type Person struct {
Id primitive.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `json:"name"`
Pets []struct {
Name string `json:"name"`
} `json:"pets"
}
When JSON is sent to the POST Person API without the pets field, the MongoDB document recorded has pets field set as null. I think this is because slices in go has zero value of nil instead of empty array?
personPostRequest := ds.Person{}
if err := c.ShouldBindJSON(&personPostRequest); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
Having pets initialized as null is troublesome because I can't use addToSet when adding pets to the document:
// Will render the error Cannot apply $addToSet to non-array field.
// Field named 'pets' has non-array type null
err = collection.FindOneAndUpdate(
ctx,
bson.M{"_id": personId},
bson.M{
"$addToSet": bson.M{
"pets": bson.M{"$each": pets},
},
},
&opt,
)
I can solve this problem by adding the struct tag bson:,omitempty to pets but I like to explore the solution where I can initialized pets to empty array in MongoDB.
How do I do this in Go? I'm using Go gin framework. Thanks
Related
I need to create some variation for the user, so that he can select only those users for whom he specifies a category (search by category) or those who do not have the same categories as in the array (in the code you can see the array).
I used the documentation and found this answer: operator $ne
But it is not works, I get a list of all users
func (r *Mongo) User(ctx context.Context, query *domain.Query) ([]*User, error) {
var filter interface{}
if query.Query != "" {
filter = bson.D{primitive.E{Key: "status", Value: true}, primitive.E{Key: "category", Value: query.Query}}
} else {
filter = bson.D{primitive.E{Key: "status", Value: true}}
} - this works
if query.OtherCategory {
category := []string{"it", "medical", "sport"}
filter = bson.M{"status": true, "category": bson.M{"$ne": category}}
} - this it is not works
cursor, err := r.col.Find(ctx, filter, opts)
var results []*domain.User
if err = cursor.All(ctx, &results); err != nil {
r.logger.Error().Err(err).Msg("failed find")
return nil, err
}
return results, nil
}
How to make a query to get a list of users who don't have these (array of categories in the code) categories in the array, and their status is true?
$ne ("not equal") will list you documents where the category field of the user is not the given array.
But this is not you want: you want to list users where the user has none of the given categories, or in other words the intersection of the user's categories and the given categories is empty.
For this you have to use the $nin ("not in") operator: list users where the category is not in the given array. For array fields this will be checked for all array elements.
if query.OtherCategory {
category := []string{"it", "medical", "sport"}
filter = bson.M{"status": true, "category": bson.M{"$nin": category}}
}
I'm trying to insert an array of objects and also push new objects into that same array.
The problems is that when i insert the array, i get an object inside an array inside the parent array, this is my code
I tried this
type MessageRecords struct {
Sender primitive.ObjectID `json:"sender" bson:"sender"
Message string `json:"message" bson:"message"`
}
type Room struct {
ID primitive.ObjectID `json:"_id" bson:"_id"`
Records []MessageRecords
}
and this is the code when i insert
mRecords := &MessageRecords{
Sender : "62a54s3a21sd65a43a21ascas" //dummy ObjectID
Message : "This is a dummy message",
}
var room Room
room.ID = primitive.NewObjectID()
room.Records = append(room.Records, mRecords)
db.dummyDB.insertOne(ctx,room)
But i get this result
"records": [
[
{
"sender": {
"$oid": "62cb43f59059283041b80da9"
},},
"message": "second message"
}
]
]
i get the object inside a an array and inside another array.
Hopefully anyone can help me, Thank you
I already figured it out, what i did in the models section is this
type MessageRecords struct {
Sender primitive.ObjectID `json:"sender" bson:"sender"
Message string `json:"message" bson:"message"`
}
type Room struct {
ID primitive.ObjectID `json:"_id" bson:"_id"`
Records interface{} `json:"records,omitempty" bson:"records,omitempty"
}
And when insert the document i do this instead
var room Room
room.ID = primitive.NewObjectID()
db.dummyDB.insertOne(ctx,room)
Now i don't append nothing
But when i want to add a message i do this
var payload MessageRecords
err := json.NewDecoder(w).Decode(&payload)
if err != nil {
// do something
}
var load Room
load.Records = payload
// here i just push the object into the interface that is converted as array
err := appendMessageInDB(load)
if err != nil {
// do something
}
And this is the result i get
"records": [
{
"sender": {
"$oid": "ObjectID"
},
"message": "first message"
},
{
"sender": {
"$oid": "ObjectID"
},
"message": "second message"
},
{
"sender": {
"$oid": "objectID"
},
"message": "third message"
}
]
Hopefully i help somebody that is running with this problem :)
if somebody wants the code of how to update the array of recors, let me know!!
I am using Golang/Fiber + Mongo driver.
I have simple struct for blog post:
type Post struct {
ID primitive.ObjectID `json:"_id" bson:"_id,omitempty"`
Title *string `json:"title" bson:"title"`
Slug *string `json:"slug" bson:"slug"`
Content []interface{} `json:"content" bson:"content,omitempty"` // same as any[]
CreatedAt time.Time `json:"created_at" bson:"created_at"`
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
PublishedAt *time.Time `json:"published_at" bson:"published_at"`
}
In content I put array of objects:
[
{
data: 'blah blah'
},
{
data: 'blah blah'
}
]
Method itself is pretty straightforward:
func GetPostBySlug(slug string) (Post, error) {
post := Post{}
filter := bson.M{
"slug": slug,
}
database, error := Database.GetMongoDatabase()
if error != nil {
println(error)
}
collection := database.Collection(model_name)
err := collection.FindOne(ctx, filter).Decode(&post)
if err != nil {
return post, err
}
return post, nil
}
And this is what I get:
"_id": "000000000000000000000000",
"title": "Test",
"slug": "test",
"content": [ // array of arrays? what??
[
{
"Key": "data",
"Value": "blah blah"
},
{
"Key": "data",
"Value": "blah blah"
},
]
],
"created_at": "2022-06-07T21:08:04.261Z",
"updated_at": "2022-07-20T21:42:36.717Z",
"published_at": null
In mongodb the content field is saved exactly as I passed it, but when I am trying to get the document, the content transforms to this weird array of arrays with key-value pairs.
If I save something like:
content: [
{
data: {
test: 'test',
what: 'what'
}
}
]
It will transform to this:
content: [
[
Key: "data",
Value: [
{
Key: "test",
Value: "test"
},
{
Key: "what",
Value: "what"
},
]
]
]
I understand the reasons behind this (this is just golang's way to handle JSON?) and assume there should be an extra step somewhere in the middle, but I have no idea what exactly I need to do
Modify type of Content to interface{} or just string.
The reason for what you see is because you use interface{} for the element type of Content:
Content []interface{}
If you use interface{}, it basically carries no type information what type the driver should use when umarshaling the array elements, so the driver will choose / use bson.D to represent the documents of the content field. bson.D is a slice that holds the ordered list of fields of the documents, that's why you see an "array of arrays". Each bson.D is a slice, representing a document.
type D []E
type E struct {
Key string
Value interface{}
}
If you can model the array elements with a struct, use that, e.g.:
type Foo struct {
Bar string
Baz int
}
Content []Foo `json:"content" bson:"content,omitempty"`
// Or a pointer to Foo:
Content []*Foo `json:"content" bson:"content,omitempty"`
If you don't have a fixed model for the array elements, alternatively you may use bson.M which is a map (but the fields / properties will be unordered which may or may not be a problem):
type M map[string]interface{}
Using it:
Content []bson.M `json:"content" bson:"content,omitempty"`
#icza is correct. However, only use []bson.M if it's an array...I had to use bson.M instead because of the structuring of my data.
I've got two models, Dictionary and DictionaryRecord with a foreign key on Dictionary.
type DictionaryRecord struct {
Id string `gorm:"primaryKey"`
Name string
DictionaryId string
}
type Dictionary struct {
Id string `gorm:"primaryKey"`
Name string
Records []DictionaryRecord
}
I'd like to make sure that when creating a dictionary with a nested record changed, to have the change reflected during the upsert.
package main
import (
// Sqlite driver based on GGO
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func main() {
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(
&DictionaryRecord{},
&Dictionary{},
)
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.AssignmentColumns([]string{"name"}),
}).Create(&Dictionary{
Name: "AAAAA",
Id: "e831ab86-db60-4a71-ba63-f20a181cd69b",
Records: []DictionaryRecord{
{
Id: "66f73e9b-61b8-4bc9-941d-80d7fd80f8f4",
Name: "will be updated",
},
},
})
}
How do you specify that the column name in the DictionaryRecord must be updated?
can you try use pointer
type DictionaryRecord struct {
Id string `gorm:"primaryKey"`
Name string
DictionaryId string
}
type Dictionary struct {
Id string `gorm:"primaryKey"`
Name string
Records []*DictionaryRecord //on this records
}
I have inserted data into mongodb in the following way
{
"_id" : ObjectId("5c80e9cc3bf127cfc80ba5dc"),
"resp" : [
{
"name" : "by",
"gender" : "synced",
"age" : "response",
"hobby" : "submitted",
"mobile" : "revision"
},
{
"name" : "byoooooo",
"gender" : "sytewed",
"age" : "se",
"hobby" : "subed",
"mobile" : "revissaaon"
}
]
}
Using this method
func (d *CollectDAO) Insert(responses []*models.FormData) error {
resp := models.Responses{
Data: responses,
}
err := db.C(formsCollection).Insert(resp)
return err
}
This is the struct used in the insert method
type FormData struct {
Name string `csv:"name" json:"name" bson:"name"`
Gender string `csv:"gender" json:"gender" bson:"gender"`
Age string `csv:"age" json:"age" bson:"age"`
Hobby string `csv:"hobby" json:"hobby" bson:"hobby"`
MobileNo string `csv:"mobile" json:"mobile" bson:"mobile"`
}
The handler reads sample csv data from a file. This is the csv data
name,gender,age,hobby,mobile
by,synced,response,submitted,revision
byoooooo,sytewed,se,subed,revissaaon
And then inserts that into mongo
When querying all the documents of my collection I get an empty response
func (d *CollectDAO) FindAll() (models.Responses, error) {
var responses []models.Responses
err := db.C(formsCollection).Find(nil).All(&responses)
if err != nil {
log.Fatal(err)
}
log.Printf("all docs %v\n", responses)
return responses, err
}
When I try to log the value of the struct I get an empty struct. This is the responses struct that I am using at the end to put the slice of response in.
type Responses struct {
Data []*FormData `json:"response"`
}
What am I doing wrong? I just need to implement a handler that will return all the data in a collection as a struct.
On the client side I get this error
unexpected end of JSON input
{ObjectIdHex("") []}
The mgo package uses the bson tag to map struct fields to document fields in MongoDB.
So your Responses type should look something like this:
type Responses struct {
Data []*FormData `json:"response" bson:"resp"`
}
Note that you have to save documents with this struct (with bson tag) to be able to fetch results into values of this type.