Golang find a value from a map of nested json data from MongoDB - mongodb

I am trying to receive data from my MongoDB using MGO in a map of type []map[string]interface{}
My JSON looks like this -
{
"_id":"string",
"brandId":123,
"category":{
"television":[
{
"cat":"T1",
"subCategory":[
{
"subCat":"T1A TV",
"warrantyPeriod":6
}
],
"warrantyPeriod":12
},
{
"cat":"T2",
"subCategory":[
{
"subCat":"T2A",
"warrantyPeriod":18
},
{
"subCat":"T2B",
"warrantyPeriod":9
}
],
"warrantyPeriod":15
},
{
"cat":"T3",
"subCategory":[
{
"subCat":"T3A",
"warrantyPeriod":3
},
{
"subCat":"T3B",
"warrantyPeriod":5
},
{
"subCat":"T3C",
"warrantyPeriod":7
},
{
"subCat":"T3D",
"warrantyPeriod":11
}
],
"warrantyPeriod":4
}
],
"television_warrantyPeriod":24
},
"title":"BrandName"
}
I would ideally pass in the category name i.e. 'television' and cat and subCat values which could be optional.
For e.g. something like this -
{
"categorySlug": "television",
"brandId": "123",
"model": "T2"
}
In which case I would expect to find '15' which is the warrantyPeriod value for T2 if there are no T2A or T2B specified.
My query functions look like this -
var data map[string]string
err := json.NewDecoder(r.Body).Decode(&data)
log.Println(err)
var buffer bytes.Buffer
buffer.WriteString("category.")
buffer.WriteString(data["categorySlug"])
brandId, _ := strconv.Atoi(data["brandId"])
concernedbrandandcategory := database.GetMappedFields("Brands", bson.M{"brandId": brandId, buffer.String(): bson.M{"$exists": true}}, bson.M{buffer.String(): 1})
categorymap := concernedbrandandcategory[0]
log.Println(categorymap["category"]["television"], reflect.TypeOf(categorymap))
My GetMappedFields function looks like this -
func GetMappedFields(collectionName string, query interface{}, selector interface{}) (result []map[string]interface{}) {
MgoSession.DB(Dbname).C(collectionName).Find(query).Select(selector).All(&result)
return
}
I'm just not able to wrap my head around this nested structure which sometimes returns a map and sometimes an interface!
Any help would be highly appreciated!

you can do something like this
majorCat := body["categorySlug"]
category := body["category"]
subCategory := body["subCategory"]
brandId, err := strconv.Atoi(body["brandId"])
if err != nil {
log.Println(err)
}
result := database.GetMappedFields("Brands", bson.M{"brandId": brandId}, bson.M{"category": 1, "_id": 0})
internalObj := result[0]["category"].(map[string]interface{})
finalValue := internalObj["television_warrantyPeriod"]
if category != "" {
for _, v := range internalObj[majorCat].([]interface{}) {
subObj := v.(map[string]interface{})
if subObj["cat"] == category {
finalValue = subObj["warrantyPeriod"]
if subCategory != "" {
minorObj := subObj["subCategory"].([]interface{})
for _, iter := range minorObj {
kevVal := iter.(map[string]interface{})
if kevVal["subCat"] == subCategory {
finalValue = kevVal["warrantyPeriod"]
}
}
}
}
}
}
Hopefully this will do dynamically or you can create a struct so that it can directly be decoded into that cheers

Related

How can I convert updateMany with ArrayFilters options mongodb query into Golang?

I am facing issue to write golang code for mongodb query. I had tried to convert following mongodb query:
db.batches.updateMany(
{"batchedOrders.newOrderObject.orderDetails.trackingId" : "ORDER_JEET_2023_02_05_01"},
{ $set: { "batchedOrders.$[elem].newOrderObject.pickupDetails.note" : "Welcome" } },
{ arrayFilters: [ { "elem.newOrderObject.orderDetails.trackingId": "ORDER_JEET_2023_02_05_01" } ] }
)
Golang code:
Here b is interface{} and carrying following data:
{
"batchedOrders.$[elem].newOrderObject.dropoffDetails.recipientDetails.email": "das#rara.delivery",
"batchedOrders.$[elem].newOrderObject.orderDetails.dimensions.height": 0,
"batchedOrders.$[elem].newOrderObject.orderDetails.dimensions.length": 0,
"batchedOrders.$[elem].newOrderObject.orderDetails.dimensions.unit": "cm",
"batchedOrders.$[elem].newOrderObject.orderDetails.dimensions.width": 0,
"batchedOrders.$[elem].newOrderObject.orderDetails.orderDeliveryDetails.sla.dropoff": 1675712794,
"batchedOrders.$[elem].newOrderObject.orderDetails.parcelSize": "Medium",
"batchedOrders.$[elem].newOrderObject.orderDetails.weightDetails.billableWeight": 5,
"batchedOrders.$[elem].newOrderObject.orderDetails.weightDetails.volWeight": 5,
"batchedOrders.$[elem].newOrderObject.orderDetails.weightDetails.weight": 5,
"batchedOrders.$[elem].newOrderObject.orderDetails.weightIndex": 1,
"batchedOrders.$[elem].newOrderObject.pickupDetails.pickupIncharge.name": "Mukesh"
}
Following is the golang code:
data, _ := bson.Marshal(b)
err = bson.Unmarshal([]byte(data), &doc)
arrayFilters := options.ArrayFilters{Filters: []interface{}{bson.D{
{Key: "elem.newOrderObject.orderDetails.trackingId", Value: trackingId},
}}}
opts := options.UpdateOptions{
ArrayFilters: &arrayFilters,
}
res, err := db.Collection(BATCH_COLLECTION_NAME).UpdateMany(ctx, bson.D{{Key: "batchedOrders.newOrderObject.orderDetails.trackingId", Value: trackingId}}, bson.M{"$set": doc}, &opts)
if err != nil {
log.Println(lg.Info(err))
return false, err
}
I am getting following error:
WriteString can only write while positioned on a Element or Value but is positioned on a TopLevel
I am trying to update multiple data in mongodb database. But getting error.
In order to successfully use the UpdateMany method, you should do something similar to this:
package main
import (
"context"
"fmt"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var (
ctx context.Context
cancel context.CancelFunc
)
type Message struct {
ID string `json:"id" bson:"id"`
Name string `json:"name" bson:"name"`
}
func main() {
ctx, cancel = context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
// set MongoDB connection
clientOptions := options.Client().ApplyURI("mongodb://root:root#localhost:27017")
mongoClient, err := mongo.Connect(ctx, clientOptions)
if err != nil {
panic(err)
}
defer mongoClient.Disconnect(ctx)
// select collection
collection := mongoClient.Database("demodb").Collection("myCollection")
defer collection.Drop(ctx)
fmt.Println("original documents")
// insert some random msg
if _, err := collection.InsertMany(ctx, []interface{}{
Message{"1", "John Doe"},
Message{"2", "Suzy Lee"},
Message{"3", "John Doe"},
}); err != nil {
panic(err)
}
// get records
var messagesTmp []bson.M
cursorTmp, err := collection.Find(ctx, bson.M{})
if err != nil {
panic(err)
}
if err := cursorTmp.All(ctx, &messagesTmp); err != nil {
panic(err)
}
for _, v := range messagesTmp {
fmt.Println(v)
}
fmt.Println(strings.Repeat("#", 100))
// update
var updateRes *mongo.UpdateResult
if updateRes, err = collection.UpdateMany(ctx,
bson.M{"name": "John Doe"},
bson.D{
bson.E{
Key: "$set",
Value: bson.D{
bson.E{
Key: "name",
Value: "John Doe - Edited",
},
},
},
},
); err != nil {
panic(err)
}
fmt.Println("num docs updated:", updateRes.ModifiedCount)
// list all
fmt.Println(strings.Repeat("#", 100))
fmt.Println("new documents")
var messages []Message
cursor, err := collection.Find(ctx, bson.M{})
if err != nil {
panic(err)
}
if err := cursor.All(ctx, &messages); err != nil {
panic(err)
}
for _, v := range messages {
fmt.Println(v)
}
}
Here, the relevant part is the // update section. First, we build the filter criteria we're going to use to fetch the documents to update. Then, we specify to $set a field name to a particular value John Doe - Edited.
The rest of the code is used to build a trivial program to demonstrate that the actual change is working. Feel free to adapt my example code to your specs and let me know if works for you too!

How to handle null value in golang?

User model
type UserExample struct {
Id primitive.ObjectID `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Location string `json:"location,omitempty"`
Title string `json:"title,omitempty"`
}
Update User
func UpdateUserExample() gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
userId := c.Param("userId")
var user models.UserExample
defer cancel()
objId, _ := primitive.ObjectIDFromHex(userId)
//Validate the request body
if err := c.BindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, responses.UserResponseExample{
Status: http.StatusBadRequest,
Message: "Error",
Data: map[string]interface{}{
"data": err.Error()},
})
}
update := bson.M{
"name": user.Name,
"location": user.Location,
"title": user.Title,
}
result, err := userCollectionExample.UpdateOne(ctx, bson.M{
"id": objId,
}, bson.M{
"$set": update,
})
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponseExample{
Status: http.StatusInternalServerError,
Message: "Error",
Data: map[string]interface{}{
"data": err.Error(),
}})
return
}
//Get Update UserExample Detail
var updateUser models.UserExample
if result.MatchedCount == 1 {
err := userCollectionExample.FindOne(ctx, bson.M{
"id": objId,
}).Decode(&updateUser)
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponseExample{
Status: http.StatusInternalServerError,
Message: "Error",
Data: map[string]interface{}{
"data": err.Error(),
}})
return
}
}
c.JSON(http.StatusOK, responses.UserResponseExample{
Status: http.StatusOK,
Message: "Success",
Data: map[string]interface{}{
"data": updateUser,
},
})
}
}
i have try update data via postman, but if value == null will be delete from collection
In this case, i want Update Title of the User, before update all data already exist
Postman
{
"title": "User One"
}
its working to change title in collection. but, for other data (name and location)has gone
"data": {
"id": "63d2ac86aeb9d78d3d5daf21",
"title": "User One",
}
so, how to handle null value from request body?
i just want change title for this case
Usually, such partial updates are handled using a structure that looks like this:
type UserUpdateRequest struct {
Id primitive.ObjectId `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Location *string `json:"location,omitempty"`
Title *string `json:"title,omitempty"`
}
Note the pointers. This way, the API caller can send non-nil values for the field it wants to update. It can also use an empty string to set the field values to empty.
Then on the database side, you have to create an update statement:
updateFields:=bson.M{}
if request.Name!=nil {
updateFields["name"]=*request.Name
}
if request.Location!=nil {
updateFields["location"]=*request.Location
}
// etc.
update:=bson.M{"$set":updateFields}
Then use the update to update the database record.

pq: malformed array literal:

I want to insert json array request like
{
"name": "Sales Commission",
"commission_type":"ZCo6QCHOBth7MD-8sohHQ",
"time_period_from": "2022-05-13 12:00:00",
"time_period_to": "2022-05-18 12:00:00",
"service_id": [
{
"service_id": "1",
"service_type": "1",
"commission_type": "1",
"commission_rate": "1.50"
}
],
"package_id": [
{
"package_id": "1",
"package_type": "1",
"commission_type": "1",
"commission_rate": "1.50"
}
],
"product_id": [
{
"product_id": "1",
"product_type": "1",
"commission_type": "1",
"commission_rate": "1.50"
}
],
"employee_id": [
{
"employee_id": "1",
"commission_type": "1",
"commission_rate": "10.50"
}
],
"status": "active"
}
I am using Golang with Postgres. I had made struct of the Model which is below:
type AutoGenerated struct {
Name string `json:"name"`
CommissionType string `json:"commission_type"`
TimePeriodFrom string `json:"time_period_from"`
TimePeriodTo string `json:"time_period_to"`
Services []Services `json:"service_id"`
Packages []Packages `json:"package_id"`
Products []Products `json:"product_id"`
Employees []Employees `json:"employee_id"`
Status string `json:"status"`
}
type Services struct {
ServiceID string `json:"service_id"`
ServiceType string `json:"service_type"`
CommissionType string `json:"commission_type"`
CommissionRate string `json:"commission_rate"`
}
type Packages struct {
PackageID string `json:"package_id"`
PackageType string `json:"package_type"`
CommissionType string `json:"commission_type"`
CommissionRate string `json:"commission_rate"`
}
type Products struct {
ProductID string `json:"product_id"`
ProductType string `json:"product_type"`
CommissionType string `json:"commission_type"`
CommissionRate string `json:"commission_rate"`
}
type Employees struct {
EmployeeID string `json:"employee_id"`
CommissionType string `json:"commission_type"`
CommissionRate string `json:"commission_rate"`
}
Request which is being decoded
var ReqMapData model.AutoGenerated
err := json.NewDecoder(r.Body).Decode(&ReqMapData)
if err != nil {
log.Println(err)
}
While performing data insertion I am facing this issue like "pq: malformed array literal: "[{"service_id":"1","service_type":"1","commission_type":"1","commission_rate":"1.50"}]"""
Code which inserts data
serJson, errJson := json.Marshal(reqMap.Services)
if errJson != nil {
util.CheckErrorLog(r, errJson)
return false
}
pkgJson, errJson1 := json.Marshal(reqMap.Packages)
if errJson1 != nil {
util.CheckErrorLog(r, errJson1)
return false
}
proJson, errJson2 := json.Marshal(reqMap.Products)
if errJson2 != nil {
util.CheckErrorLog(r, errJson2)
return false
}
empJson, errJson3 := json.Marshal(reqMap.Employees)
if errJson3 != nil {
util.CheckErrorLog(r, errJson3)
return false
}
Qry.WriteString("INSERT INTO commission_template(id, name, commission_type, time_period_from, time_period_to, service_id, package_id, product_id, employee_id, status) ")
Qry.WriteString(" VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);")
err := ExecuteNonQuery(Qry.String(), nanoid, reqMap.Name, reqMap.CommissionType, reqMap.TimePeriodFrom, reqMap.TimePeriodTo, string(serJson), string(pkgJson), string(proJson), string(empJson), reqMap.Status)
if err != nil {
util.CheckErrorLog(r, err)
return false
}
// ExecuteNonQuery - Update, Insert Query
func ExecuteNonQuery(SQLQry string, params ...interface{}) error {
_, err := VDB.Exec(SQLQry, params...)
return err
}

query with aggregate in mongodb and go (match, lookup)

I need that when passing a search parameter I can obtain the subcategories with the categories, also obtain the total data of all the subcategories. I don't know if it can be done with bson.M. I leave my code.
This function is the one I use for categories, I used it for subcategories but adding $lookup to bson.M throws an error.
func SubcategoriesGet(c *fiber.Ctx) error {
categoryCollection := config.MI.DB.Collection("subcategories")
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
var categories []models.Category
filter := bson.M{}
findOptions := options.Find()
if s := c.Query("s"); s != "" {
filter = bson.M{
"$or": []bson.M{
{
"name": bson.M{
"$regex": primitive.Regex{
Pattern: s,
Options: "i",
},
},
},
// {
// "visible": bson.M{
// "$regex": primitive.Regex{
// Pattern: s,
// Options: "i",
// },
// },
// },
},
}
}
if visible := c.Query("visible"); visible == "1" {
filter = bson.M{"visible": true}
}
if visible := c.Query("visible"); visible == "0" {
filter = bson.M{"visible": false}
}
page, _ := strconv.Atoi(c.Query("page", "1"))
limitVal, _ := strconv.Atoi(c.Query("limit", "10"))
var limit int64 = int64(limitVal)
total, _ := categoryCollection.CountDocuments(ctx, filter)
findOptions.SetSort(bson.M{"_id": -1})
findOptions.SetSkip((int64(page) - 1) * limit)
findOptions.SetLimit(limit)
// findOptions.
cursor, err := categoryCollection.Find(ctx, filter, findOptions)
defer cursor.Close(ctx)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"success": false,
"message": "Users not found",
"error": err,
})
}
for cursor.Next(ctx) {
var category models.Category
cursor.Decode(&category)
categories = append(categories, category)
}
last := math.Ceil(float64(total / limit))
if last < 1 && total > 0 {
last = 1
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"data": categories,
"total": total,
"page": page,
"last_page": last,
"limit": limit,
})
}
and this is the code that advances, I only need to obtain the total number of documents and be able to do a search by subcategory name.
func SubcategoriesGet(c *fiber.Ctx) error {
subcategoryCollection := config.MI.DB.Collection("subcategories")
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
var subcategory []models.Subategory
// querys
page, _ := strconv.Atoi(c.Query("page", "1")) // page
limitVal, _ := strconv.Atoi(c.Query("limit", "10")) // limit
var limit int64 = int64(limitVal)
// matchState := bson.D{}
if s := c.Query("s"); s != "" {
// bson.D{{"$regexMatch", bson.D{{"input", "$name"}, {"regex", "/g/"}}}}
// matchState = bson.M{"$match": bson.M{"wordname": bson.M{"$in": []bson.RegEx{{"^how$", "i"}}}}}
}
// pipeline
lookupState := bson.D{{"$lookup", bson.D{{"from", "categories"}, {"localField", "idcategory"}, {"foreignField", "_id"}, {"as", "category"}}}}
unwindState := bson.D{{"$unwind", bson.D{{"path", "$category"}, {"preserveNullAndEmptyArrays", false}}}}
limitState := bson.D{{"$limit", limit}}
sortState := bson.D{{"$sort", bson.D{{"_id", -1}}}}
skipState := bson.D{{"$skip", (int64(page) - 1) * limit}}
// groupState := bson.D{{"$project", bson.D{{"_id", 1}, {"name", 1}, {"category.name", 1}}}}
// orState := bson.D{{"$or", bson.D{{"name", bson.D{{"$regex", primitive.Regex{Pattern: "gec", Options: "i"}}}}}}}
// groupState := bson.D{{"$group", bson.D{{"_id", "$_id"}}}}
// countState := bson.D{{"$count", "total"}}
cursor, err := subcategoryCollection.Aggregate(ctx, mongo.Pipeline{skipState, lookupState, unwindState, limitState, sortState})
if err != nil {
panic(err)
}
// cursorTotal, err := subcategoryCollection.Aggregate(ctx, mongo.Pipeline{lookupState, unwindState, limitState, sortState})
// if err != nil {
// panic(err)
// }
// var showData []bson.M
// if err = cursor.Decode(); err != nil {
// panic(err)
// }
for cursor.Next(ctx) {
var subc models.Subategory
cursor.Decode(&subc)
subcategory = append(subcategory, subc)
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"data": subcategory,
// "total": ,
"page": page,
// "last_page": last,
"limit": limit,
})
}
I did tests with aggregate ($lookup, $skip, $limit, $count) etc. I still need to do a LIKE in SQL search and get the total number of documents

How to unmarshal non-trivial mongo return bson struct

The return of mongo is like:
[
{
"_id": "XXXXX-XXXX-XXXXXXXXXXXXXXX",
"InternetGatewayDevice": {
"WANDevice": {
"1": {
"_instance": true,
"_writable": false
},
"2": {
"_instance": true,
"_writable": false
},
...
"_object": true,
"_timestamp": {
"$date": "2020-11-23T04:14:13.202Z"
},
"_writable": false
}
}
}
]
The WanDevice.1 and WanDevice.2 is not fixed. Can be n objects.
When the return is a json string the UnmarshalJSON works. Because the id of WanDevice.[id] is sequencial and not repeat, thus I can create a array of WanDevices into InternetGatewayDevice struct
Can be a way to "unmarshal" this return from mongo without need to create a struct like this?
type Device struct {
InternetGatewayDevice InternetGatewayDevice `json:"InternetGatewayDevice" bson:"InternetGatewayDevice"`
}
type InternetGatewayDevice struct {
WANDevice WANDevice `json:"WANDevice" bson:"WANDevice"`
}
type WANDevice struct {
DeviceNum1 `json:"1" bson:"1"`
DeviceNum1 `json:"2" bson:"2"`
DeviceNum1 `json:"3" bson:"3"`
DeviceNum1 `json:"4" bson:"4"`
.....
}
I call it like this:
collection := m.Client.Database(acs.Mongo.Database).Collection("devices")
filter := bson.D{
{"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.2.MACAddress._value", parsedMac},
}
response := &models.Device{}
err = collection.FindOne(context.TODO(), filter).Decode(response)
You can create a custom UnmarshalJSON, something like this:
https://play.golang.org/p/FdW064a9RDT