convert map to bson - go

type Player struct {
id bson.ObjectId
test map[int]int
}
func (this *Player) Id() bson.ObjectId {
return this.id
}
func (this *Player) DbObj() bson.D {
testBson := bson.D{}
for k,v := range this.test {
testBson = append(testBson, bson.M{"id":k, "v":v}) // compile error
}
return bson.D{
{"_id",this.id},
{"test", testBson},
}
}
The bson document should be:
{'_id':ObjectId(xx),'test':[{'id':1,'v':1},..., {'id':2,'v':2}]}
But I don't know how to convert the map object to [{'id':1,'v':1},..., {'id':2,'v':2}], bson.M is not the type DocElem which D contain

Use a Go slice to represent the BSON array:
func (this *Player) DbObj() bson.D {
var testBson []bson.M
for k,v := range this.test {
testBson = append(testBson, bson.M{"id":k, "v":v})
}
return bson.D{
{"_id",this.id},
{"test", testBson},
}
}

Related

Unmarshal YAML into complex object which may be either struct or string

Trying to unmarshal YAML into complex object such as map[string]map[interface{}]string.
The problem is that I want to be able to differentiate an interface{} part between string and Source which is a struct.
type Source struct {
ID string `yaml:"id"`
Name string `yaml:"name"`
LogoID string `yaml:"logoId"`
URL string `yaml:"url"`
}
type UNFT struct {
ItemMeta map[string]map[interface{}]string `yaml:"item_meta"`
// could be
// ItemMeta map[string]map[string]string `yaml:"item_meta"`
// or
// ItemMeta map[string]map[Source]string `yaml:"item_meta"`
}
Obviously YAML does not know how to unmarshal into Source struct so I have to implement Unmarshaler interface:
type Unmarshaler interface {
UnmarshalYAML(value *Node) error
}
But I don't quite understand the big picture of unmarshaling process. In general I assume that I have to manually traverse *yaml.Node and call func UnmarshalYAML(value *Node) error on every node.
package main
import (
"fmt"
"gopkg.in/yaml.v3"
)
type Source struct {
ID string `json:"id"`
Name string `json:"name"`
LogoID string `json:"logoId"`
URL string `json:"url"`
}
var data = `
unf:
item_meta:
source:
!struct
? id: "data-watch"
name: "DataWatch"
logoid: "data-watch"
url: "https"
: "product_any('SS')"
public_usage:
"": "source_any('SDF')"
"provider": "source_any('ANO')"`
type UNFT struct {
ItemMeta map[string]map[interface{}]string `yaml:"item_meta"`
}
type MetaConverterConfigT struct {
UNFT UNFT `yaml:"unf"`
}
func main() {
cfg := MetaConverterConfigT{}
err := yaml.Unmarshal([]byte(data), &cfg)
if err != nil {
fmt.Println("%w", err)
}
fmt.Println(cfg)
}
func (s *UNFT) UnmarshalYAML(n *yaml.Node) error {
var cfg map[string]map[interface{}]string
if err := n.Decode(&cfg); err != nil {
fmt.Println("%w", err)
}
return nil
}
Go playground
type MetaKey struct {
String string
Source Source
}
func (k *MetaKey) UnmarshalYAML(n *yaml.Node) error {
if n.Tag == "!!str" {
return n.Decode(&k.String)
}
if n.Tag == "!!map" {
return n.Decode(&k.Source)
}
return fmt.Errorf("unsupported MetaKey type")
}
// ...
type UNFT struct {
ItemMeta map[string]map[MetaKey]string `yaml:"item_meta"`
}
https://go.dev/play/p/Nhtab4l-ANT
If you need the map type to remain as is, i.e. without adding the custom key type, then you can implement the unmarshaler on UNFT as well and just do a re-mapping with any:
type UNFT struct {
ItemMeta map[string]map[any]string `yaml:"item_meta"`
}
func (u *UNFT) UnmarshalYAML(n *yaml.Node) error {
var obj struct {
ItemMeta map[string]map[MetaKey]string `yaml:"item_meta"`
}
if err := n.Decode(&obj); err != nil {
return err
}
u.ItemMeta = make(map[string]map[any]string, len(obj.ItemMeta))
for k, v := range obj.ItemMeta {
m := make(map[any]string, len(v))
for k, v := range v {
if k.Source != (Source{}) {
m[k.Source] = v
} else {
m[k.String] = v
}
}
u.ItemMeta[k] = m
}
return nil
}
https://go.dev/play/p/uwboGKf3qnD

golang patch string values on an object, recursive with filtering

Community,
The mission
basic
Implement a func that patches all string fields on an objects
details
[done] fields shall only be patched if they match a matcher func
[done] value shall be processed via process func
patching shall be done recursive
it shall also work for []string, []*string and recursive for structs and []struct, []*struct
// update - removed old code
Solution
structs
updated the structs to use (though this does not affect the actual program, i use this for completeness
type Tag struct {
Name string `process:"yes,TagName"`
NamePtr *string `process:"no,TagNamePtr"`
}
type User struct {
ID int
Nick string
Name string `process:"yes,UserName"`
NamePtr *string `process:"yes,UserNamePtr"`
Slice []string `process:"yes,Slice"`
SlicePtr []*string `process:"yes,SlicePtr"`
SubStruct []Tag `process:"yes,SubStruct"`
SubStructPtr []*Tag `process:"yes,SubStructPtr"`
}
helper func
Further we need two helper funcs to check if a struct has a tag and to print to console
func Stringify(i interface{}) string {
s, _ := json.MarshalIndent(i, "", " ")
return string(s)
}
func HasTag(structFiled reflect.StructField, tagName string, tagValue string) bool {
tag := structFiled.Tag
if value, ok := tag.Lookup(tagName); ok {
parts := strings.Split(value, ",")
if len(parts) > 0 {
return parts[0] == tagValue
}
}
return false
}
patcher - the actual solution
type Patcher struct {
Matcher func(structFiled *reflect.StructField, v reflect.Value) bool
Process func(in string) string
}
func (p *Patcher) value(idx int, v reflect.Value, structFiled *reflect.StructField) {
if !v.IsValid() {
return
}
switch v.Kind() {
case reflect.Ptr:
p.value(idx, v.Elem(), structFiled)
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
var sf = v.Type().Field(i)
structFiled = &sf
p.value(i, v.Field(i), structFiled)
}
case reflect.Slice:
for i := 0; i < v.Len(); i++ {
p.value(i, v.Index(i), structFiled)
}
case reflect.String:
if p.Matcher(structFiled, v) {
v.SetString(p.Process(v.String()))
}
}
}
func (p *Patcher) Apply(in interface{}) {
p.value(-1, reflect.ValueOf(in).Elem(), nil)
}
how to use
func main() {
var NamePtr string = "golang"
var SubNamePtr string = "*secure"
testUser := User{
ID: 1,
Name: "lumo",
NamePtr: &NamePtr,
SubStruct: []Tag{{
Name: "go",
},
},
SubStructPtr: []*Tag{&Tag{
Name: "*go",
NamePtr: &SubNamePtr,
},
},
}
var p = Patcher{
// filter - return true if the field in struct has a tag process=true
Matcher: func(structFiled *reflect.StructField, v reflect.Value) bool {
return HasTag(*structFiled, "process", "yes")
},
// process
Process: func(in string) string {
if in != "" {
return fmt.Sprintf("!%s!", strings.ToUpper(in))
} else {
return "!empty!"
}
return in
},
}
p.Apply(&testUser)
fmt.Println("Output:")
fmt.Println(Stringify(testUser))
}
goplay
https://goplay.tools/snippet/-0MHDfKr7ax

index out of range when working with slices

I am trying to convert an old version jsonline into a new one (with a different structure).
Now the old file has the following structure
{"user": "myname", "uuid": "1242315425", "data": {"Niveau1": ["AUTRE", "RC"], "Niveau2": ["RA06"], "Niveau3": ["RA06_01"]}}
however, Niveau2 and Niveau3 are not always present and the length of the lists is not always the same.
The new file has a more complicated structure
{"user": "myname", "uuid": "1242315425","annotation":{"classifications":{"Niveau1":{"labels":[{"value":"AUTRE"}, {"value":"RC"}]}, "Niveau2": {"labels": [{"value":"RA06"}], "Niveau3": {"labels": [{"value":"RA06_01"}]}}}}
What I have done so far is (after parsing the files in appropriate structures) the following function
func convert(oldAnnots []AnnotV1) (newAnnots []AnnotV2) {
for _, element := range oldAnnots {
var newAnnot AnnotV2
newAnnot.User = element.User
newAnnot.Uuid = element.Uuid
if element.Data.Niveau1 != nil {
for i, annot1 := range element.Data.Niveau1 {
newAnnot.Annotation.Classif.Niveau1.Labels[i].Value = annot1
}
}
if element.Data.Niveau2 != nil {
for j, annot2 := range element.Data.Niveau2 {
newAnnot.Annotation.Classif.Niveau2.Labels[j].Value = annot2
}
}
if element.Data.Niveau3 != nil {
for k, annot3 := range element.Data.Niveau3 {
newAnnot.Annotation.Classif.Niveau3.Labels[k].Value = annot3
}
}
newAnnots = append(newAnnots, newAnnot)
}
return
}
However, I got the error saying the index [0] is out of range for my slice.
panic: runtime error: index out of range [0] with length 0
Definitions of the two structures are the following
type AnnotV1 struct {
Uuid string `json:"uuid"`
Data struct {
Niveau1 []string `json:"Niveau1"`
Niveau2 []string `json:"Niveau2"`
Niveau3 []string `json:"Niveau3"`
} `json:"data"`
User string `json:"user"`
}
and
type AnnotV2 struct {
Uuid string `json:"uuid"`
Annotation struct {
Classif struct {
Niveau1 struct {
Labels []struct {
Value string `json:value`
} `json:"labels"`
}
Niveau2 struct {
Labels []struct {
Value string `json:value`
} `json:"labels"`
}
Niveau3 struct {
Labels []struct {
Value string `json:value`
} `json:"labels"`
}
} `json:"classifications"`
} `json:"annotation"`
User string `json:"user"`
}
type Label struct {
Value string `json:"value"`
}
type AnnotV2 struct {
Uuid string `json:"uuid"`
Annotation struct {
Classif struct {
Niveau1 struct {
Labels []Label `json:"labels"`
}
Niveau2 struct {
Labels []Label `json:"labels"`
}
Niveau3 struct {
Labels []Label `json:"labels"`
}
} `json:"classifications"`
} `json:"annotation"`
User string `json:"user"`
}
pre-allocate the slice
if element.Data.Niveau2 != nil {
newAnnot.Annotation.Classif.Niveau2.Labels = make([]Label, len(element.Data.Niveau2))
for j, annot2 := range element.Data.Niveau2 {
newAnnot.Annotation.Classif.Niveau2.Labels[j].Value = annot2
}
}
or use append
if element.Data.Niveau2 != nil {
for _, annot2 := range element.Data.Niveau2 {
newAnnot.Annotation.Classif.Niveau2.Labels = append(newAnnot.Annotation.Classif.Niveau2.Labels, Label{annot2})
}
}

Call dynamic types' interface method

I'm trying to write a common module using Go interface. It results in an error:
panic: interface conversion: interface {} is main.AmazonOrder, not main.OrderMapper
Code:
package main
type AmazonOrder struct {
OrderId string
ASIN string
}
func (o AmazonOrder) Generalise() *Order {
return &Order{
ChannelOrderId: o.OrderId,
}
}
type EbayOrder struct {
OrderId string
SKU string
}
func (o EbayOrder) Generalise() *Order {
return &Order{
ChannelOrderId: o.OrderId,
}
}
type Order struct {
ID string
ChannelOrderId string
}
type OrderMapper struct {
Generalise func() *Order
}
var orderFactory = map[string]func() interface{} {
"amazon": func() interface{} {
return AmazonOrder{}
},
"ebay": func() interface{} {
return EbayOrder{}
},
"vanilla": func() interface{} {
return Order{}
},
}
func main() {
orderType := "amazon"
initialiseOrder := orderFactory[orderType]
anOrder := initialiseOrder()
// Unmarshal from json into anOrder etc.. here.
theOrder := anOrder.(OrderMapper).Generalise()
println(theOrder.ChannelOrderId)
}
In plain sight logic, this should work fine. But, definitely I'm misunderstanding the type conversion in Go. TIA for clarifying what it is.
Playground: https://play.golang.org/p/tHCzKGzEloL
you have to use interface, not a struct with function field, i left comments around the ganges.
// OrderMapper is probably ment to be an interface. You can convert interface
// to struct only if its the original one. Though you need to convert it into iterface in
// your case because type of return value is variable. As all orders implement
// 'Generalise' and you are planing to convert empty interface to OderMapper why not just
// return order mapper right away? also best practice to name your interface, with just
// one method, is methodName + 'er' so Generalizer.
type OrderMapper interface {
Generalise() *Order
}
/* alternative
var orderFactory = map[string]func() OrderMapper{
"amazon": func() OrderMapper {
return AmazonOrder{OrderId: "amazonOrderID"}
},
"ebay": func() OrderMapper {
return EbayOrder{}
},
"vanilla": func() OrderMapper {
return Order{}
},
}
*/
var orderFactory = map[string]func() interface{}{
"amazon": func() interface{} {
return AmazonOrder{OrderId: "amazonOrderID"} // i added a value to verify correctness
},
"ebay": func() interface{} {
return EbayOrder{}
},
"vanilla": func() interface{} {
return Order{}
},
}
func main() {
orderType := "amazon"
initialiseOrder := orderFactory[orderType]
anOrder := initialiseOrder()
// Unmarshal from json into anOrder etc.. here.
// alternative: theOrder := anOrder.Generalise()
theOrder := anOrder.(OrderMapper).Generalise()
println(theOrder.ChannelOrderId == "amazonOrderID") // true
}

Converting Slice of custom type to slice of string

I have multiple structs like bellow
type Person struct {
first string
last string
age int
}
type Product struct {
ProductID int
Name string
Description string
Price float64
}
I want a function that will take a slice of any type as the first argument and a function as a second argument that it will use to call with each element of the slice to construct a string slice and will return that string slice. Something like map() in Typescript/scala or select() in C#.
Since Go doesn't have generics, "any type" can only mean interface{}. You can have something like this:
func ToStringSlice(arr []interface{}, convert func(interface{}) string) []string {
ret := []string{}
for _, elem := range arr {
ret = append(ret, convert(elem))
}
return ret
}
Then you can basically inject any conversion function you want. e.g.
fmt.Println(ToStringSlice([]interface{}{1, 2, "foo", "bar"},
func(x interface{}) string {
return fmt.Sprint(x)
}))
And since string conversions can go bad, I'd also add error checking:
// Define the function with an error return.
type Stringifier func(interface{}) (string, error)
func ToStringSlice(arr []interface{}, convert Stringifier) ([]string, error) {
ret := []string{}
for _, elem := range arr {
if s, e := convert(elem); e != nil {
return nil, e
} else {
ret = append(ret, s)
}
}
return ret, nil
}
one solution involves fmt.Stringer
package main
import (
"fmt"
)
func main() {
items := []fmt.Stringer{Product{ProductID: 1}, Person{first: "first"}}
var res []string
for _, i := range items {
res = append(res, i.String())
}
fmt.Println(res)
}
type Person struct {
first string
last string
age int
}
func (p Person) String() string { return p.first }
type Product struct {
ProductID int
Name string
Description string
Price float64
}
func (p Product) String() string { return fmt.Sprint(p.ProductID) }
That's a typical use case for an interface. Luckily enough the interface you need already exists as fmt.Stringer (https://golang.org/pkg/fmt/#Stringer):
Have a look at https://play.golang.org/p/d1sNPLKhNCU...
First your structs are required to implement the Stringer interface. This is done by simply adding a menthode String(), for example:
type Product struct {
ProductID int
Name string
Description string
Price float64
}
func (p Product) String() string {
return fmt.Sprintf("%d %s", p.ProductID, p.Name)
}
Now your Product is also a Stringer and can be Therefore be passed in any function that accepts Stringers:
func AsStringSlice(in []fmt.Stringer) []string {
out := []string{}
for _, i := range in {
out = append(out, i.String())
}
return out
}
Since "interface{} says nothing" (https://go-proverbs.github.io/) I would recommend to use interface{} as rarely as possible. This is achieved in this approach. Also you can avoid reflection that way.
On the other hand you have to be able to manage the structs, eg. implement the String function. If thats not possible because the Types come from a dependency consider wrapping them:
type MyTypeWithStringer package.ForeinTypeWithoutStringer
func(t MyTypeWithStringer) String() string {
// ...
}
This is a solution that fits me best
type Person struct {
first string
last string
age int
}
type Product struct {
ProductID int
Name string
Description string
Price float64
}
func mapToStringSlice(slice interface{}, mapFunc func(interface{}) string) []string {
s := reflect.ValueOf(slice)
if s.Kind() != reflect.Slice {
panic("mapToStringSlice() given a non-slice type")
}
ret := make([]string, s.Len())
for i:=0; i<s.Len(); i++ {
ret[i] = mapFunc(s.Index(i).Interface())
}
return ret
}
func main() {
persons := []Person{{
first: "A",
last: "g",
age: 20,
},{
first: "B",
last: "r",
age: 40,
},{
first: "C",
last: "",
age: 0,
}}
products := []Product{Product{ProductID: 1}, Product{ProductID: 2}}
personFirstNames := mapToStringSlice(persons, func(input interface{}) string {
return input.(Person).first
})
productIDs := mapToStringSlice(products, func(input interface{}) string {
return input.(Product).ProductID
})
}

Resources