Passing dynamic array struct to a function Golang - mongodb

I want to create function that accept 'dynamic array struct' and use it to map a data from database *mgodb
type Cats struct {
Meow string
}
func getCatsPagination() {
mapStructResult("Animality","Cat_Col", Cats)
}
type Dogs struct {
Bark string
}
func getDogsPagination() {
mapStructResult("Animality","Dog_Col", Dogs)
}
func mapStructResult(db string, collection string, model interface{}) {
result := []model{} //gets an error here
err := con.Find(param).Limit(int(limit)).Skip(int(offset)).All(&result) // map any database result to 'any' struct provided
if err != nil {
log.Fatal(err)
}
}
and gets an error as "model is not a type", why is it?
any answer will be highly appreciated !

Pass the ready slice to the mapStructResult function.
type Cats struct {
Meow string
}
func getCatsPagination() {
cats := []Cats{}
mapStructResult("Animality", "Cat_Col", &cats)
}
type Dogs struct {
Bark string
}
func getDogsPagination() {
dogs := []Dogs{}
mapStructResult("Animality", "Dog_Col", &dogs)
}
func mapStructResult(db string, collection string, model interface{}) {
err := con.Find(param).Limit(int(limit)).Skip(int(offset)).All(result) // map any database result to 'any' struct provided
if err != nil {
log.Fatal(err)
}
}

First of all there is no term in go called dynamic struct. Struct fields are declared before using them and cannot be changed. We can use bson type to handle the data. Bson type is like map[string]interface{} used to save the data dynamically.
func mapStructResult(db string, collection string, model interface{}) {
var result []bson.M // bson.M works like map[string]interface{}
err := con.Find(param).Limit(int(limit)).Skip(int(offset)).All(&result) // map any database result to 'any' struct provided
if err != nil {
log.Fatal(err)
}
}
For more information on bson type. Look this godoc for BSON

Related

Incorrect decoding in Mongo-go-driver

For example, I have this structure:
type Overview struct {
Symbol string `json:"Symbol,omitempty"`
AssetType string `json:"AssetType,omitempty"`
Name string `json:"Name,omitempty"`
Description string `json:"Description,omitempty"`
...
...
}
In addition to this, I have several other structures.
My function selects a suitable structure for Decode(), but when I try to get data from the database, I get the result in this form:
[
{
"Key": "_id",
"Value": "618aa6f2a64cb8105a9c7984"
},
{
"Key": "Symbol",
"Value": "IBM"
},
{
"Key": "FiscalYearEnd",
"Value": "December"
},
...
...
]
I expect a response in the form of my structure, but I get such an array. I tried declaring the structure for the response myself: var result models.Overview. After that, the problem disappeared, but this is not the solution for my problem
There is my function:
var (
models map[string]interface{}
)
func init() {
models = make(map[string]interface{})
models["Overview"] = models.Overview{}
models["Earnings"] = models.Earnings{}
...
...
}
func GetDbData(collection string, db *mongo.Database, filter bson.D) (interface{}, error) {
var result = models[collection] // Choosing a structure
res := db.Collection(collection).FindOne(context.TODO(), filter)
err := res.Decode(&result)
if err != nil {
return nil, err
}
return result, nil
}
I can't understand why this is happening, I hope that someone has already encountered this problem and will be able to help me
https://jira.mongodb.org/browse/GODRIVER-988
Another approach to solve this can be by first decoding it into bson.M type and then unmarshalling it to your struct. Yes, this is not optimal.
eg:
func GetMonthStatusByID(ctx context.Context, id string) (interface{}, error) {
var monthStatus interface{}
filter := bson.M\{"_id": id}
err := db.Collection("Months").FindOne(ctx, filter).Decode(&monthStatus)
return monthStatus, err
}
The above snippet should be changed to:
func GetMonthStatusByID(ctx context.Context, id string) (interface{}, error) {
var monthStatus interface{}
filter := bson.M\{"_id": id}
tempResult := bson.M{}
err := db.Collection("Months").FindOne(ctx, filter).Decode(&tempResult)
if err == nil {
obj, _ := json.Marshal(tempResult)
err= json.Unmarshal(obj, &monthStatus)
}
return monthStatus, err
}

How to insert a map[string] into a jsonb field

I have a struct Huygens:
type Huygens struct {
Worlds map[string]World `json:"worlds" sql:"type:JSONB"`
}
type World struct {
Diameter int
Name string
}
When I try to insert into a Postgres DB using GORM I get:
sql: converting argument $1 type: unsupported type map[string]huygens.world, a map
To insert I simply use db.Create(&World{})
Does anyone know how to insert that column as a JSONB column and avoid that error?
Thanks
You need to specify that Huygens is a gorm.Model and dump your World struct to a []byte type.
import (
"encoding/json"
"fmt"
"gorm.io/datatypes"
)
type Huygens struct {
gorm.Model
Worlds datatypes.JSON
}
type World struct {
Diameter int
Name string
}
world := World{Diameter: 3, Name: Earth}
b, err := json.Marshal(world)
if err != nil {
fmt.Println("error:", err)
}
db.Create(&Huygens{Worlds: datatypes.JSON(b)})
You can see more here.
Another way, which makes the model more usable, is to create a type that implements Scan/Value, and in those Unmarshal from/Marshal to JSON:
type Huygens struct {
Worlds WorldsMap `json:"worlds"`
}
type World struct {
Diameter int
Name string
}
type WorldsMap map[string]World
func (WorldsMap) GormDataType() string {
return "JSONB"
}
func (w *WorldsMap) Scan(value interface{}) error {
var bytes []byte
switch v := value.(type) {
case []byte:
bytes = v
case string:
bytes = []byte(v)
default:
return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
}
err := json.Unmarshal(bytes, w)
return err
}
func (w WorldsMap) Value() (driver.Value, error) {
bytes, err := json.Marshal(s)
return string(bytes), err
}
Then you can use the Worlds field in your code like a map, as opposed to an awkward byte slice.

How to unmarshal a BSON string value into a custom type using go-mongo-driver?

I'm using MarshalBSONValue to marshal an inner struct field to a custom string representation.
I can't figure out how to implement the inverse operation, UnmarshalBSONValue, in order to parse the custom string representation into an inner struct.
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
type inner struct {
value string
}
func (i inner) MarshalBSONValue() (bsontype.Type, []byte, error) {
return bsontype.String, []byte(i.value), nil
}
// How to implement this?
//
// func (i *inner) UnmarshalBSONValue(t bsontype.Type, data []byte) error {
// ...
// }
type Outer struct {
Inner inner `bson:"inner"`
}
func TestMarshalBSON(t *testing.T) {
var outer1 = Outer{Inner: inner{value: "value"}}
doc, err := bson.Marshal(outer1)
assert.NoError(t, err)
fmt.Printf("%#v\n", string(doc)) // "\x11\x00\x00\x00\x02inner\x00value\x00"
var outer2 Outer
err = bson.Unmarshal(doc, &outer2) // error
assert.NoError(t, err)
assert.Equal(t, outer1, outer2)
}
I would greatly appreciate it if anyone could provide a working implementation of UnmarshalBSONValue for the example test above.
You can use bsoncore.ReadString to parse the given value.
func (i inner) MarshalBSONValue() (bsontype.Type, []byte, error) {
return bson.MarshalValue(i.value)
}
func (i *inner) UnmarshalBSONValue(t bsontype.Type, value []byte) error {
if t != bsontype.String {
return fmt.Errorf("invalid bson value type '%s'", t.String())
}
s, _, ok := bsoncore.ReadString(value)
if !ok {
return fmt.Errorf("invalid bson string value")
}
i.value = s
return nil
}

How to marshal/unmarshal bson array with polymorphic struct with mongo-go-driver

I'm struggling to marshal/unmarshal bson array with polymorphic struct with mongo-go-driver。I plan to save a struct discriminator into the marshaled data, and write a custom UnmarshalBSONValue function to decode it according to the struct discriminator. But I don't know how to do it correctly.
package polymorphism
import (
"fmt"
"testing"
"code.byted.org/gopkg/pkg/testing/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
type INode interface {
GetName() string
}
type TypedNode struct {
NodeClass string
}
type Node struct {
TypedNode `bson:"inline"`
Name string
Children INodeList
}
func (n *Node) GetName() string {
return n.Name
}
type INodeList []INode
func (l *INodeList) UnmarshalBSONValue(t bsontype.Type, data []byte) error {
fmt.Println("INodeList.UnmarshalBSONValue")
var arr []bson.Raw // 1. First, try to decode data as []bson.Raw
err := bson.Unmarshal(data, &arr) // error: cannot decode document into []bson.Raw
if err != nil {
fmt.Println(err)
return err
}
for _, item := range arr { // 2. Then, try to decode each bson.Raw as concrete Node according to `nodeclass`
class := item.Lookup("nodeclass").StringValue()
fmt.Printf("class: %v\n", class)
if class == "SubNode1" {
bin, err := bson.Marshal(item)
if err != nil {
return err
}
var sub1 SubNode1
err = bson.Unmarshal(bin, &sub1)
if err != nil {
return err
}
*l = append(*l, &sub1)
} else if class == "SubNode2" {
//...
}
}
return nil
}
type SubNode1 struct {
*Node `bson:"inline"`
FirstName string
LastName string
}
type SubNode2 struct {
*Node `bson:"inline"`
Extra string
}
With code above, I'm trying to decode INodeList data as []bson.Raw, then decode each bson.Raw as a concrete Node according to nodeclass. But it report error:
cannot decode document into []bson.Raw
at line
err := bson.Unmarshal(data, &arr).
So, how to do it correctly?
You need to pass a bson.Raw’s pointer to bson.Unmarshal(data, &arr) and then slice its value to an array of raw values like:
func (l *INodeList) UnmarshalBSONValue(t bsontype.Type, data []byte) error {
fmt.Println("INodeList.UnmarshalBSONValue")
var raw bson.Raw // 1. First, try to decode data as bson.Raw
err := bson.Unmarshal(data, &raw)
if err != nil {
fmt.Println(err)
return err
}
// Slice the raw document to an array of valid raw values
rawNodes, err := raw.Values()
if err != nil {
return err
}
// 2. Then, try to decode each bson.Raw as concrete Node according to `nodeclass`
for _, rawNode := range rawNodes {
// Convert the raw node to a raw document in order to access its "nodeclass" field
d, ok := rawNode.DocumentOK()
if !ok {
return fmt.Errorf("raw node can't be converted to doc")
}
class := d.Lookup("nodeclass").StringValue()
// Decode the node's raw doc to the corresponding struct
var node INode
switch class {
case "SubNode1":
node = &SubNode1{}
case "SubNode2":
node = &SubNode2{}
//...
default:
// ...
}
bson.Unmarshal(d, node)
*l = append(*l, node)
}
return nil
}
Note that Note.Children must be a INodeList's pointer, and the inline fields must be a struct or a map (not a pointer):
type Node struct {
TypedNode `bson:",inline"`
Name string
Children *INodeList
}
type SubNode1 struct {
Node `bson:",inline"`
FirstName string
LastName string
}
type SubNode2 struct {
Node `bson:",inline"`
Extra string
}

MongoDB bson.M query

I am trying to query using bison all JSON data in MongoDB with two fields but am getting null as result.
{
"allowedList": [
{
"List": [
{
"allow": {
"ss": 1,
},
"Information": [
{
"Id": "Id1"
}
]
}
]
}
]
}
I was able to filter all using the MongoDB at command line using
db.slicedb.find({"allowedList.List.allow.ss":1,"allowedList.List.Information.nsiId":"Id-Id21"})
but using
query := bson.M{"allowedList.List.allow": bson.M{"ss": sst}, "allowedList.List.Information": bson.M{"Id": Id}}
sst and Id are integer and string input to the query function
err := db.C(COLLECTION).Find(query).All(&specificSlices)
but is not working, am getting null even though there are json data that match the two field. Can someone help point out what was wrong with my query?
Server and database config
type SliceDataAccess struct {
Server string
Database string
}
var db *mgo.Database
const (
COLLECTION = "slicedb"
)
Establish a connection to database
func (m *SliceDataAccess) Connect() {
session, err := mgo.DialWithTimeout(m.Server, 20*time.Second)
if err != nil {
log.Fatal(err)
}
db = session.DB(m.Database)
}
Structs fields
type InstanceInfo struct {
ID string `json:"nfId" bson:"_id"`
AllowedList []AllowedNssai `json:"allowedList" bson:"allowedList"`
}
type AllowedNssai struct {
List []AllowedSnssai `json:"List,omitempty" bson:"List"`
...
}
type AllowedSnssai struct {
Allow *Snssai `json:"allow,omitempty" bson:"allow"`
Information []NsiInformation `json:"Information,omitempty" bson:"Information"`
}
type NsiInformation struct {
Id string `json:"Id" bson:"Id"`
}
type Snssai struct {
Ss int32 `json:"sst" bson:"ss"`
}
Query function defined
func (m *SliceDataAccess) FindAll(sst int32, nsiId string ([]InstanceInfo, error) {
var specificSlices []InstanceInfo
query := bson.M{"allowedList.List.allow": bson.M{"ss": sst}, "allowedList.List.Information": bson.M{"Id": nsiId}}
err := db.C(COLLECTION).Find(query).All(&specificSlices)
if err != nil {
return specificSlices, err
}
return specificSlices, nil
}
HTTP handler function for request and response
func AvailabilityGet(w http.ResponseWriter, r *http.Request)
var slice InstanceInfo
err := json.NewDecoder(r.Body).Decode(&slice)
if err != nil {
respondWithError(w, http.StatusBadRequest, "Object body not well decoded")
return
}
sst := slice.AllowedList[0].List[0].Allow.Sst
nsiId := slice.AllowedList[0].List[0].Information[0].Id
specificSlices, err := da.FindAll(sst, nsiId)
json.NewEncoder(w).Encode(specificSlices)
}
Attached is my the full go code i have done.
this worked
query := bson.M{"allowedNssaiList.allowedSnssaiList.allowedSnssai.sst": sst, "allowedNssaiList.allowedSnssaiList.nsiInformationList.nsiId": nsiId}