Nested struct conversion from mgo (mongodb library) to golang struct? - go

I have a struct like this:
type ArticleDocument struct {
ArticleID string `json:"article_id" bson:"article_id"`
ArticleTitle string `json:"article_title" bson:"article_title"`
Author string `json:"author" bson:"author"`
Board string `json:"board" bson:"board"`
Content string `json:"content" bson:"content"`
Date string `json:"date" bson:"date"`
IP string `json:"ip" bson:"ip"`
MessageConut struct {
All int `json:"all" bson:"all"`
Boo int `json:"boo" bson:"boo"`
Count int `json:"count" bson:"count"`
Neutral int `json:"neutral" bson:"neutral"`
Push int `json:"push" bson:"push"`
} `json:"message_count,inline" bson:"message_count,inline"`
Messages []interface{} `json:"messages" bson:"messages"`
URL string `json:"url" bson:"url"`
}
In MongoDB, my document looks like:
{
"_id" : ObjectId("5ab1da8133691b034b2be31d"),
"article_id" : "M.1521548086.A.DCA",
"article_title" : "some title",
"author" : "somebody",
"board" : "some board",
"content" : "some content",
"date" : "Tue Mar 20 20:14:42 2018",
"ip" : "1.1.1.1",
"message_conut" : {
"all" : 15,
"boo" : 0,
"count" : 14,
"neutral" : 1,
"push" : 14
},
"messages" : [],
"url" : "https://xxx.xxx.xxx"
}
In my Golang code, i am trying to use mgo to query this document and print it out, however, all document fields are converted to golang struct correctly except the nested document "MessageConut", all the values in "All", "Boo", "Count", ...etc are zero:
message_count":{"all":0,"boo":0,"count":0,"neutral":0,"push":0}"
Could you please guide how do I solve this issue?

Related

Go Template - remove specific field

I am having following data format:
{"time":"2022-08-24T06:00:00Z","duration":0,"level":"OK","data":{"series":[{"name":"gnb_kpi","tags":{"ID":"1017","_field":"Success_rate%","cluster_id":"ec17-1017","swversion":"6.0"},"columns":["time","_value"],"values":[["2022-08-24T06:00:00Z","100"]]}]},"previousLevel":"CRITICAL","recoverable":true}
I want to remove the _time field from the columns array and similarily the timestamp from values array. The output I want is like this:
{"time":"2022-08-24T06:00:00Z","duration":0,"level":"OK","data":{"series":[{"name":"gnb_kpi","tags":{"ID":"1017","_field":"Success_rate%","cluster_id":"ec17-1017","swVersion":"6.0"},"columns":["_value"],"values":[["100"]]}]},"previousLevel":"CRITICAL","recoverable":true}
I used the online service JSON-to-Go to generate a data structure that corresponds to your input. It produced
type AutoGenerated struct {
Time time.Time `json:"time"`
Duration int `json:"duration"`
Level string `json:"level"`
Data struct {
Series []struct {
Name string `json:"name"`
Tags struct {
ID string `json:"ID"`
Field string `json:"_field"`
ClusterID string `json:"cluster_id"`
Swversion string `json:"swversion"`
} `json:"tags"`
Columns []string `json:"columns"`
Values [][]interface{} `json:"values"`
} `json:"series"`
} `json:"data"`
PreviousLevel string `json:"previousLevel"`
Recoverable bool `json:"recoverable"`
}
The algorithm is simple:
parse JSON into the generated structure
iterate over series
find the position of time field in columns
remove the corresponding data elements from values
https://go.dev/play/p/rjnvmdBXCE4
Output is (beautified)
{
"time": "2022-08-24T06:00:00Z",
"duration": 0,
"level": "OK",
"data": {
"series": [
{
"name": "gnb_kpi",
"tags": {
"ID": "1017",
"_field": "Success_rate%",
"cluster_id": "ec17-1017",
"swversion": "6.0"
},
"columns": [
"_value"
],
"values": [
[
"100"
]
]
}
]
},
"previousLevel": "CRITICAL",
"recoverable": true
}
As you see, no time

Keeping multiple types ( nesting types / embedding types ) in a single collection:

I want to keep all of the types in a single collection named “BenchmarkDatasets”. Do I need to declare the subtypes(LatData, AggregateData, MetaData) differently or do I just need to accept that I’ll have a collection for every type?
Any help is greatly appreciated.
Here's the Schema I generated:
type LatData {
LatResults: [[Int ]]
LatResultSize: [Int ]
}
type AggregateData {
EVRCounter: Int
EVRLatencyTotal: Int
EVRLatencyAverage: Float
LatTestCount: Int
LatencyTotal: Int
LatencyAverage: Float
}
type MetaData {
StartTimeUTC: String
EndTimeUTC: String
StartTimeLocal: String
EndTimeLocal: String
}
type BenchmarkDataset {
LatData: LatData
AggregateData: AggregateData
MetaData: MetaData
}
type Query {
allBenchmarkDatasets: [BenchmarkDataset!]
}
And here's the data I want to shove into "BenchmarkDatasets":
{
"MetaData" :
{
"StartTimeUTC" : "Sun Oct 18 21:41:38 2020\n",
"EndTimeUTC" : "Sun Oct 18 21:45:38 2020\n",
"StartTimeLocal" : "Sun Oct 18 16:41:38 2020\n",
"EndTimeLocal" : "Sun Oct 18 16:45:38 2020\n"
},
"AggregateData" :
{
"EVRCounter" : 3,
"EVRLatencyTotal" : 70,
"EVRLatencyAverage" : 23.333333333333332,
"LatTestCount" : 159,
"LatencyTotal" : 11871,
"LatencyAverage" : 74.660377358490564
},
"LatData" :
{
"LatResultSize" :
[
4,
4,
4
],
"LatResults" :
[
[
0,
2,
"zoom",
"latencymonitor"
],
[
1,
1,
"zoom",
"latencymonitor"
],
[
2,
1,
"zoom",
"latencymonitor"
],
[
3,
1,
"dota2",
"dota2"
]
]
}
}
Also, I know that my data is not well formatted(specifically the 2d "LatData" array that contains 2 ints and 2 strings), and any data format tips are also appreciated.
Figured out the issue! I specifically needed to use the "#embedded" directive to make my schema look something like this:
type LatData #embedded {
LatResults: [[Int ]]
LatResultSize: [Int ]
}
type AggregateData #embedded {
EVRCounter: Int
EVRLatencyTotal: Int
EVRLatencyAverage: Float
LatTestCount: Int
LatencyTotal: Int
LatencyAverage: Float
}
type MetaData #embedded {
StartTimeUTC: String
EndTimeUTC: String
StartTimeLocal: String
EndTimeLocal: String
}
type BenchmarkDataset {
LatData: LatData
AggregateData: AggregateData
MetaData: MetaData
}
type Query {
allBenchmarkDatasets: [BenchmarkDataset!]
}

Find all elements sorted by best matches

I have some entities in my database called "events". Each of these events contain an array of string, called "tags".
I want to make a query to get all the events matching an array of tags that I will give in parameter.
BUT, I want these events to be sorted like:
The first one is the one containing most of the tags I give in parameter.
The second one is the second one containing most of the tags I give in parameter.
And so on.
If there are more than one event containing the same amount of tags I want them to be sorted by their "name" property in alphabetical order.
Example:
"name" = "event1", "tags" = ["food", "music", "gaming", "sport"]
"name" = "event2", "tags" = ["gaming"]
"name" = "event3", "tags" = ["music", "sport"]
"name" = "event4", "tags" = ["food", "music", "gaming", "sport"]
"name" = "event5", "tags" = ["music", "sport", "coding"]
"name" = "event6", "tags" = ["coding"]
"name" = "event7", "tags" = ["food", "gaming", "sport"]
The array of tags that I give in parameter for this example is: ["food", "music", "gaming", "sport"]
The result for this example will be an array of events, containing in that order:
event1, event4, event7, event3, event5, event2
To get all the events containing the tags I simply make a query with the "$or" operators. This allowed me to get all the events if they contained at least one of the tag given in parameter.
Code:
var events []model.Event
var MyQuery []map[string]interface{}
for i := 0; i < len(tags); i++ {
currentCondition := bson.M{"tags": tags[i]}
MyQuery = append(MyQuery, currentCondition)
}
err := dbEvents.C(collectionEvents).Find(bson.M{"$or": OrQuery}).All(&events)
But i really don't know how to sort them like I showed you.
package main
import (
"fmt"
"github.com/ahmetb/go-linq"
)
type T struct {
Name string
Tags []string
}
func main() {
params := []string{"food", "music", "gaming", "sport"}
t := []T{
T{Name: "event1", Tags: []string{"food", "music", "gaming", "sport"}},
T{Name: "event2", Tags: []string{"gaming"}},
T{Name: "event3", Tags: []string{"music", "sport"}},
T{Name: "event4", Tags: []string{"food", "music", "gaming", "sport"}},
T{Name: "event5", Tags: []string{"music", "coding", "sport"}},
T{Name: "event6", Tags: []string{"coding"}},
T{Name: "event7", Tags: []string{"food", "gaming", "sport"}},
}
var result []T
linq.From(t).SortT(func(t1 T, t2 T) bool {
var rs1 []string
linq.From(t1.Tags).IntersectByT(linq.From(params), func(str string) string {
return str
}).ToSlice(&rs1)
var rs2 []string
linq.From(t2.Tags).IntersectByT(linq.From(params), func(str string) string {
return str
}).ToSlice(&rs2)
return len(rs1) > len(rs2)
}).ToSlice(&result)
fmt.Printf("%+v", result)
}
[{Name:event1 Tags:[food music gaming sport]} {Name:event4 Tags:[food music gaming sport]} {Name:event7 Tags:[food gaming sport]} {Name:event3 Tags:[music sport]} {Name:event5 Tags:[music coding sport]} {Name:event2 Tags:[gaming]} {Name:event6 Tags:[coding]}]
Above program sorts the array as per your requirements, Hope this will help you.

Remove Element From Struct But Only For This One Function

So I have an Struct that holds data that has a AddedByUser which links to my User Struct.
What I want to be able to do it remove the UserLevel from the AddedByUser
Now I want to be able to do it from this function only, so using the json:"-" is not an option. That would remove it from all json output. I only want to remove it form this one function.
I should also say that these are Gorm models and when I have been trying to remove the 10 option (UserLevels) it only removes the outer data set not the UserLevel from all of the data.
{
"ID": 1,
"CreatedAt": "2019-01-08T16:33:09.514711Z",
"UpdatedAt": "2019-01-08T16:33:09.514711Z",
"DeletedAt": null,
"UUID": "00000000-0000-0000-0000-000000000000",
"Title": "title000",
"Information": "info999",
"EventDate": "2006-01-02T15:04:05Z",
"AddedByUser": {
"ID": 2,
"CreatedAt": "2019-01-08T15:27:52.435397Z",
"UpdatedAt": "2019-01-08T15:27:52.435397Z",
"DeletedAt": null,
"UUID": "b019df80-a7e4-4397-814a-795e7e84b4ca",
"Firstname": "Me",
"Surname": "admin",
"Password": "....",
"Email": "admin#email.co.uk",
"UserLevel": {
"ID": 0,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"LevelTitle": "",
"UserLevel": null
},
So this is what I have tried,
data := []models.MyData{}
data = append(data[0:2])
I have about 14 results, with out the append it loads all the results but with this is only loads two results. The idea was to remove either UpdateAt or Title. As I am not sure if the gorm model information is all 0 or if the slice sees them as 0,1,2,3,4 etc.
I have also tried to range over the slice of models, while I can access each of the sections, I can not seem to find a simple method to remove data by name from a struct? Maps seem to have that but not structs which I am not sure why?
Thanks.
UPDATE
This is the model I am using:
//Model
type MyData struct {
gorm.Model
UUID uuid.UUID
Title string
Information string
EventDate time.Time
AddedByUser Users `gorm:"ForeignKey:added_by_user_fk"`
AddedByUserFK uint
}
//Users Model
type Users struct {
gorm.Model
UUID uuid.UUID
Firstname string
Surname string
Password string
Email string
UserLevel UserLevels `gorm:"ForeignKey:user_level_fk" json:",omitempty"`
UserLevelFK uint
}
As mentioned in the comments, you cannot remove fields from a struct value, because that would yield a value of a different type.
However, you can set fields to their zero value. Combined with the omitempty JSON tag, you can exclude fields from the JSON encoding. To make this work properly, you have to change the UserLevel field to a pointer type (otherwise you end up with empty objects in the JSON document).
Types shortened for brevity:
package main
import (
"encoding/json"
"fmt"
)
type MyData struct {
Title string
AddedByUser Users
}
type Users struct {
ID int
UserLevel *UserLevels `json:",omitempty"` // pointer type with omitempty
}
type UserLevels struct {
LevelTitle string
}
func main() {
var x MyData
x.Title = "foo"
x.AddedByUser.ID = 2
x.AddedByUser.UserLevel = &UserLevels{}
f(x)
b, _ := json.MarshalIndent(x, "", " ")
fmt.Println("main:\n" + string(b))
}
func f(x MyData) {
// "unset" UserLevel. Since we are receiving a copy of MyData, this is
// invisible to the caller.
x.AddedByUser.UserLevel = nil
b, _ := json.MarshalIndent(x, "", " ")
fmt.Println("f:\n" + string(b))
}
// Output:
// f:
// {
// "Title": "foo",
// "AddedByUser": {
// "ID": 2
// }
// }
// main:
// {
// "Title": "foo",
// "AddedByUser": {
// "ID": 2,
// "UserLevel": {
// "LevelTitle": ""
// }
// }
// }
Try it on the playground: https://play.golang.org/p/trUgnYamVOA
Alternatively, you can define new types that exclude the AddedByUser field. However, since this field isn't at the top level, this is a lot of work, and it's easy to forget to update those types when new fields are added to the original types.
If the field were at the top level, the compiler would do most of the work for you, because types that only differ in their field tags can be directly converted to one another:
type MyData struct {
ID int
Title string
}
func main() {
var x MyData
x.ID = 1
x.Title = "foo"
f(x)
}
func f(x MyData) {
type data struct { // same as MyData, except the field tags
ID int
Title string `json:"-"`
}
b, _ := json.MarshalIndent(data(x), "", " ")
fmt.Println("main:\n" + string(b))
}

How to do multi-level pull of array element in mgo?

I want to do a multi-level array element delete. My Structs are as follows:-
type Company struct {
Id bson.ObjectId `bson:"_id,omitempty"`
CompanyName string
Process []ProcessItem
}
type ProcessItem struct{
SortOrder int
Documents []DocumentTemplate
}
type DocumentTemplate struct {
Id bson.ObjectId `bson:"_id,omitempty"`
TemplateName string
}
I want to delete an object of type DocumentTemplate. The DocumentTemplate is a struct array in ProcessItem which is a struct array in Company struct. I have Company Id(field of struct Company) and TemplateName(field of struct DocumentTemplate).
I tried the below mgo pull query but it is not working.
c := db.C("company")
pullQuery := bson.M{"process": bson.M{"documents.templatename": "xyz"}}
err := c.Update(bson.M{"_id": "123"}, bson.M{"$pull": pullQuery})
Please point out the mistakes I made here. Thanks.
Edit:
Adding one example document for the clarity of the question
{
"_id" : ObjectId("573da7dddd73171e42a84045"),
"companyname" : "AAA",
"process" : [
{
"processname" : "Enquiry",
"sortorder" : 0,
"documents" : [
{
"templatename" : "xyz",
"processname" : "Enquiry"
},
{
"templatename" : "ss",
"processname" : "Enquiry"
}
]
},
{
"processname" : "Converted",
"processtype" : 1,
"sortorder" : 2,
"documents" : [
{
"templatename" : "dd",
"processname" : "Converted"
},
{
"templatename" : "fg",
"processname" : "Converted"
}
]
}
]
}
I need to pull out just one DocumentTemplete record, like the one below:
{
"templatename" : "xyz",
"processname" : "Enquiry"
}
N.B: TemplateName will be unique inside a Company.
You'll need to use the $ positional operator (https://docs.mongodb.com/manual/reference/operator/projection/positional/). In order to be able to use that you'll also have to add to your query the following:
"process.documents.templatename": "xyz"
Your Update statement should look like this:
c := db.C("company")
pullQuery := bson.M{"process.$.documents": bson.M{"templatename": "xyz"}}
err := c.Update(bson.M{"_id": "123", "process.documents.templatename": "xyz"}, bson.M{"$pull": pullQuery})
You can pull values in array from the array in mongo record
change2 := bson.M{
"$pull": bson.M{"sapinfo.systemstatus": bson.M{"$in": tags}},
}

Resources