get slice of specified field from slice of structs - go

I have a slice of structs that contains 10 cars like:
type struct car {
engine
window
wheel
}
so the slice cars contains 10 car struct.
I would like to know if a function exist such as:
var engines string[] = cars.Getfield("engine") // engines will contain 10 engines names

There's no library function for this.
You can implement manually using reflect package
Example:
type Cars []Car
func (cars Cars) getFieldString(field string) []string {
var data []string
for _, car := range cars {
r := reflect.ValueOf(car)
f := reflect.Indirect(r).FieldByName(field)
data = append(data, f.String())
}
return data
}
Code in Playground here

There's no generics in Go (not until 2.0 at least), so there's not a lot of helper functions out there.
If you need to use this function often, you can implement it as a method for type engines.

Answering #RodolfoAP question under #Joe-Akanesuvan answer (not enough rep to post as a comment):
Even though generics are a part of Go now, there're no functional programming libs in std, I used 1st one I found on awesome-go, so this is probably isn't production-ready code, but that's generally what it would look like:
package main
import (
"fmt"
fp "github.com/repeale/fp-go"
)
type data struct {
field string
anotherField int
}
func main() {
fmt.Printf(
"result: %+v",
fp.Map(func(d data) string { return d.field })(
[]data{
{
field: "apple",
anotherField: 1,
},
{
field: "orange",
anotherField: 2,
},
{
field: "banana",
anotherField: 3,
},
},
),
)
}
Code output:
result: [apple orange banana]
Code in playground here

Related

Merge structs with some overlapping fields

I saw several questions asking on how to merge unique structs and how to merge identical structs.
But how would I merge structs that have some overlap? and which fields get taken & when?
e.g.:
type structOne struct {
id string `json:id`
date string `json:date`
desc string `json:desc`
}
and
type structTwo struct {
id string `json:id`
date string `json:date`
name string `json:name`
}
how would I merge it such that I get
{
id string `json:id`
date string `json:date`
desc string `json:desc`
name string `json:name`
}
also, what happens if in this case the two id's are the same (assuming a join over id's) but the names are different?
In javascript, doing something like Object.assign(structOne, structTwo).
Go is a strongly typed language, unlike javascript you can't merge two struct into one combined struct because all type are determined at compile-time. You have two solution here :
Using embedded struct:
One great solution is to use embedded struct because you don't have to merge anything anymore.
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
)
// Shared field
type common struct {
ID string `json:id`
Date string `json:date`
}
type merged struct {
// Common field is embedded
common
Name string `json:name`
Desc string `json:desc`
}
func main() {
buf := bytes.Buffer{}
buf.WriteString("{ \"id\": \"1\", \"date\": \"27/07/2020\", \"desc\": \"the decription...\" }")
merged := &merged{}
err := json.Unmarshal(buf.Bytes(), merged)
if err != nil {
log.Fatal(err)
}
// Look how you can easily access field from
// embedded struct
fmt.Println("ID:", merged.ID)
fmt.Println("Date:", merged.Date)
fmt.Println("Name:", merged.Name)
fmt.Println("Desc:", merged.Desc)
// Output:
// ID: 1
// Date: 27/07/2020
// Name:
// Desc: the decription...
}
If you want to read more about struct embedding:
golangbyexample.com
travix.io
Using Maps
Another solution is to use maps but you will loose the benefits of struct and methods. This example is not the simplest but there is some great example in the other responses.
In this example I'm using Mergo. Mergo is library that can merge structs and map. Here it is used for creating maps object in the Map methods but you can totally write your own methods.
package main
import (
"fmt"
"log"
"github.com/imdario/mergo"
)
type tOne struct {
ID string
Date string
Desc string
}
// Map build a map object from the struct tOne
func (t1 tOne) Map() map[string]interface{} {
m := make(map[string]interface{}, 3)
if err := mergo.Map(&m, t1); err != nil {
log.Fatal(err)
}
return m
}
type tTwo struct {
ID string
Date string
Name string
}
// Map build a map object from the struct tTwo
func (t2 tTwo) Map() map[string]interface{} {
m := make(map[string]interface{}, 3)
if err := mergo.Map(&m, t2); err != nil {
log.Fatal(err)
}
return m
}
func main() {
dst := tOne{
ID: "destination",
Date: "26/07/2020",
Desc: "destination object",
}.Map()
src := tTwo{
ID: "src",
Date: "26/07/1010",
Name: "source name",
}.Map()
if err := mergo.Merge(&dst, src); err != nil {
log.Fatal(err)
}
fmt.Printf("Destination:\n%+v", dst)
// Output:
// Destination:
// map[date:26/07/2020 desc:destination object iD:destination name:object name
}
Go structs and JavaScript objects are very different. Go structs do not have dynamic fields.
If you want dynamic key/value sets that you can easily iterate over and merge, and are very JSON friendly, why not a map[string]interface{}?
$ go run t.go
map[a:1 b:4]
map[a:1 b:4 c:3]
$ cat t.go
package main
import(
"fmt"
)
type MyObj map[string]interface{}
func (mo MyObj)Merge(omo MyObj){
for k, v := range omo {
mo[k] = v
}
}
func main() {
a := MyObj{"a": 1, "b": 4}
b := MyObj{"b": 2, "c": 3}
b.Merge(a)
fmt.Printf("%+v\n%+v\n", a, b)
}
you can use github.com/fatih/structs to convert your struct to map. Then iterate over that map and choose which fields need to copy over. I have a snippet of code which illustrates this solution.
func MergeStruct (a structOne,b structTwo) map[string]interface{}{
a1:=structs.Map(a)
b1:=structs.Map(b)
/* values of structTwo over writes values of structOne */
var myMap=make(map[string]interface{})
for val,key:=range(a1){
myMap[key]=val
}
for val,key:=range(b1){
myMap[key]=val
}
return myMap
}
You can use the reflect package to do this. Try iterating through the two structs and then you can either use another struct type to store the values or maybe use a map.
Check out this question to find out how you can iterate over a struct.
Check out this question to find out how to get the name of the fields.
Remember to use exported field names for the reflect package to work.
Here is an example which works.

Update a slice in a struct passed to a function

Need help in understanding how to update a slice that is contained in a struct and passed to a function.
Function addBookToShelfInLibrary(l *library, shelfid int, b book) - takes as input a library, tries to add to book to the shelf with the id = shelfid (passed as param). The function appends to the books array and also assigns it to books array. What am I missing?
At the end of code run, I expect the books to contain two books, "harrypotter", "bible" but I see only one, i.e. harrypotter. Also, I am passing a pointer to the library but I don't think that matters in this case.
playground code:- https://play.golang.org/p/JrjtLSj-jHI
func main() {
lib := library{
shelves: []shelf{
{
id: 1,
books: []book{
{name: "harrypotter"},
},
},
},
}
addBookToShelfInLibrary(&lib, 1, book{name: "bible"})
fmt.Printf("%v", lib)
}
type library struct {
shelves []shelf
}
type shelf struct {
id int
books []book
}
type book struct {
name string
}
func addBookToShelfInLibrary(l *library, shelfid int, b book) {
for _, s := range (*l).shelves {
if s.id == shelfid {
//found shelf, add book
s.books = append(s.books, b)
}
}
}
Thanks for your answer / explanation in advance.
In this statement:
s := range (*l).shelves
the variable s is a copy of the slice element. The later call to append modifies this copy, the not slice element. Change the code to modify the slice element:
func addBookToShelfInLibrary(l *library, shelfid int, b book) {
for i := range l.shelves {
s := &l.shelves[i]
if s.id == shelfid {
//found shelf, add book
s.books = append(s.books, b)
}
}
}
Another approach is to use a pointer to a shelf:
type library struct {
shelves []*shelf
}
lib := library{
shelves: []*shelf{
{
...
All other code remains the same. Run it on the playground.

Retrieve from a map by comparing the key to a string array

I have some Go code that is returning a map of values but I only need some of the results. Is there a way I can test/filter the key of the map against a string array (or something similar) to give a simplified result rather than a bunch of if statements? All the samples I have looked up had fixed values to filter against.
A simple example is below, but rather than supplying the string I want to have a list of possible values so I can get a reduced list.
package main
import "fmt"
type colors struct {
animal string
COLOR []string
}
func main() {
// Map animal names to color strings.
colors := map[string]string{
"bird": "blue",
"snake": "green",
"cat": "black",
}
// Display string.
fmt.Println(colors)
}
Yes, you can test/filter a map using range. If you have all the possible values, you can simply compare them to the map using a key/value lookup and make the structs based off of that.
package main
import (
"fmt"
)
type colors struct {
animal string
COLOR []string
}
func main() {
//the list of values
possibleValues := []string{"bird","dog", "cat"}
// Map animal names to color strings.
foo := map[string]string{
"bird": "blue",
"snake": "green",
"cat": "black",
}
//slice of objects of your struct
objects := []colors{}
//for every value in the possible values
for _, v := range possibleValues {
//if it's in the map, make a new struct and append it to the slice of objects
if val,ok := foo[v]; ok {
objects = append(objects, colors{animal:v,COLOR:[]string{val}})
}
}
// Display string.
fmt.Println(objects)
}
https://play.golang.org/p/njD6E_WssHT

Unwanted types for append in []struct

I'm learning go and stuck structs
I need to generate json:
{
"and" : [
{ "term" : { "name.second" : "ba" } }
]
}
}
So i can do it with code:
package main
import (
"encoding/json"
"fmt"
)
type Term map[string]interface{}
type TermHash struct {
Term `json:"term"`
}
type Filter struct {
And []TermHash `json:"and"`
}
func main() {
var filter Filter
filter.And = append(filter.And, TermHash{ Term{"name.second" : "ba"}})
jsonFilter, _ := json.MarshalIndent(filter, "", " ")
fmt.Printf(string(jsonFilter))
}
But I really do not want use separate TermHash and Term types, its seems unnecessary in code and used only to add this lines to filter. Can i avoid of using it?
I just want to accomplish this with only type Filter:
type Filter struct {
And []struct{
Term map[string]interface{} `json:"term"`
} `json:"and"`
}
This looks more readable and represent expected result, but I can't create instances of Term in this way. There is a way to add Terms line to json without creating separate types?
What you really want is a custom way to JSON-encode Term. Rather than a simple map, it marshals as if it were an object containing a map. So, let's write that custom MarshalJSON for it:
type Term map[string]interface{}
func (t Term) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
T map[string]interface{} `json:"term"`
}{t})
}
We create an anonymous struct here, fill it with ourselves, and then marshal it to JSON. Note the use of map[string]interface{} here. While that looks like Term, it's actually a different type with its own way of being JSON encoded. If you tried to save some typing here and use T Term, you'd find yourself in an infinite loop. (This idea of creating new types that have the same structure as other types is a major part of Go.)
Now our data structure is simple; just a slice of Term:
type Filter struct {
And []Term `json:"and"`
}
func main() {
var filter Filter
filter.And = append(filter.And, Term{"name.second" : "ba"})
jsonFilter, _ := json.MarshalIndent(filter, "", " ")
fmt.Printf(string(jsonFilter))
}
That said, you could also go the other way and make your data model more closely match the JSON. In that case Term should be a struct, not a map. You'd probably write it this way:
type Term struct {
Values map[string]interface{} `json:"term"`
}
func NewTerm(key, value string) Term {
return Term{map[string]interface{}{key: value}}
}
type Filter struct {
And []Term `json:"and"`
}
func main() {
var filter Filter
filter.And = append(filter.And, NewTerm("name.second", "ba"))
jsonFilter, _ := json.MarshalIndent(filter, "", " ")
fmt.Printf(string(jsonFilter))
}
You can create a wrapper function around that.
package main
import (
"fmt"
"encoding/json"
)
type Filter struct {
And []map[string]interface{} `json:"and"`
}
func WrapFilter(i interface{}) Filter {
return Filter{
And: []map[string]interface{}{
map[string]interface{}{
"term": i,
},
},
}
}
func main() {
f := WrapFilter(map[string]string{"name.second": "ba"})
json.Marshal(f)
}
This way, you will always have "term" has the second-level key in your marshaled JSON.

Golang Map struct in another one

I'm new working on golang, In this case I have a map [string] which has a struct.
At this point everything is works.
But I would like to have a map[string] in which I can access at the same time to another map[string] which has it self struct.
This is the code in which I'm trying to work.
type myStruct struct{
atrib1 string
atrib2 string
}
var apiRequest map[string] map[string]myStruct
I would like acces to something like this:
func main() {
apiRequest = make(map[string] map[string]myStruct)
apiKeyTypeRequest["Key"]["MyFirstOption"].atrib1 = "first Value first op"
apiKeyTypeRequest["Key"]["MyFirstOption"].atrib2 = "second Value first op"
apiKeyTypeRequest["Key"]["MysecondtOption"].atrib1 = "first Value second op"
}
An alternative to using a map inside a map, is to have a single map[mapKey] where mapKey is a struct:
type mapKey struct {
Key string
Option string
}
The benefits is that you only have to do a single lookup when searching for a myStruct, and you only have to create a single map.
The downside is in case you need to be able to get that options map[string]myStruct map, since it does not exist. Also, you cannot check if a certain key exists or not, because keys and options exists in pairs.
Working example:
package main
import "fmt"
type myStruct struct {
atrib1 string
atrib2 string
}
type mapKey struct {
Key string
Option string
}
func main() {
apiKeyTypeRequest := make(map[mapKey]myStruct)
apiKeyTypeRequest[mapKey{"Key", "MyFirstOption"}] = myStruct{"first Value first op", "second Value first op"}
apiKeyTypeRequest[mapKey{"Key", "MysecondtOption"}] = myStruct{atrib1: "first Value second op"}
fmt.Printf("%+v\n", apiKeyTypeRequest)
}
Playground: http://play.golang.org/p/tGd7ja7QI2
To expand upon previous answers, each map must be declared and instantiated (as well as the struct at the end of the map), that means you'll need to instantiate the "outer" map
mapOfMaps := make(map[string]map[string]myStruct)
as well as the "inner" map(s) for each key you have.
mapOfMaps[key] = make(map[string]myStruct)
The obvious issue you run into here is how do you dynamically check to see if the mapOfMaps[key] has already been instantiated? You do this using the following syntax:
if _, ok := mapOfMaps[key]; !ok {
mapOfMaps[key] = make(map[string]myStruct)
}
This syntax checks to see if mapOfMaps already has an instantiated value for that key, and if not instantiates it. Similarly, you can use the same syntax to instantiate the structs you are using by checking to see of mapOfMaps[key][key2] has been instantiated yet.
if _, ok := mapOfMaps[key][key2]; !ok {
mapOfMaps[key][key2] = new(myStruct)
}
Now you can access the struct through the call to 2 keys and an attribute of the struct
fmt.Println(mapOfMaps[key][key2].atrib1)
As #FUZxxl has said, you need to initialize the sub-map for each outer map, but there is a shorthand syntax for it:
type myStruct struct {
atrib1 string
atrib2 string
}
func main() {
var myMap = map[string]map[string]myStruct{
"foo": {
"bar": {attrib1: "a", attrib2: "b"},
"baz": {"c", "d"}, //or by order...
},
"bar": {
"gaz": {"e", "f"},
"faz": {"g", "h"},
},
}
fmt.Println(myMap["foo"]["bar"].atrib1)
fmt.Println(myMap["bar"]["gaz"].atrib2)
}

Resources