0

Came from javascript background, and just started with Golang. I am learning all the new terms in Golang, and creating new question because I cannot find the answer I need (probably due to lack of knowledge of terms to search for)

I created a custom type, created an array of types, and I want to create a function where I can retrieve all the values of a specific key, and return an array of all the values (brands in this example)

type Car struct {
  brand string
  units int
}

....

var cars []Car
var singleCar Car

//So i have a loop here and inside the for-loop, i create many single cars
singleCar = Car {
   brand: "Mercedes",
   units: 20
}

//and i append the singleCar into cars
cars = append(cars, singleCar)

Now what I want to do is to create a function that I can retrieve all the brands, and I tried doing the following. I intend to have key as a dynamic value, so I can search by specific key, e.g. brand, model, capacity etc.

func getUniqueByKey(v []Car, key string) []string {
    var combined []string

    for i := range v {
        combined = append(combined, v[i][key]) 
        //this line returns error -
        //invalid operation: cannot index v[i] (map index expression of type Car)compilerNonIndexableOperand
    }
    return combined
    //This is suppose to return ["Mercedes", "Honda", "Ferrari"]
}

The above function is suppose to work if i use getUniqueByKey(cars, "brand") where in this example, brand is the key. But I do not know the syntaxes so it's returning error.

Someone Special
  • 12,479
  • 7
  • 45
  • 76
  • 1
    You can't access a field on a struct like you would an index on a map. the `key` variable is set at runtime (its value could be coming from an HTTP request, for example), whereas the names of the fields are defined at compile time. All in all, this really does look like an X-Y problem. Why would you create a type, and need to access data like this? – Elias Van Ootegem Jun 06 '22 at 09:08
  • 1
    *"Using "dynamic" key to extract value from map"* -- `Car` is not a `map`. `Car` is a `struct`. Maps and structs are two very different things in Go. – mkopriva Jun 06 '22 at 09:11
  • Pardon me but coming from JS background, I'm used to putting data I retrieved from API in to a organized object (e.g. cars). In the above scenario, I can work with `v[i].brand` but not `v[i][key]`, it's because it's not possible? – Someone Special Jun 06 '22 at 09:11
  • @mkopriva guess I mess up with the terms. It's an Array (map) of Cars (struct).. – Someone Special Jun 06 '22 at 09:11
  • ```v[i].brand``` is quite possible in Go but not ```v[i][something dynamic]``` as Car is not map. It is similar to as JS Object. – Supritam Jun 06 '22 at 09:15
  • Thanks @Supritam. So you are suggesting I should not use `struct`? I'm having difficult understanding object vs struct/map. What I wanted to do is to organise those data in to an array of objects and I assume struct is the way to go. I cannot on the web how exactly to use key-value pair – Someone Special Jun 06 '22 at 09:17
  • ```struct``` ~ ```Object``` . Map is Key-Value pair. I may suggest you go for Array of Map. in that case your Car will not have a fixed structure. – Supritam Jun 06 '22 at 09:20
  • @Supritam, may I know if i were to use `singleCar = map[string]interface{}{ brand: 'mercedes'....}`, how should I declare `car` as a array, which holds all the `singleCar`? – Someone Special Jun 06 '22 at 09:47
  • In your case map[string]interface{} means it is a map whose key is string and value is of any type. To declare an array of type map you need to use ```var cars =[]map[string]interface{}```, then create ```singleCar := make(map[string]interface{}) car["brand"] = "Mercedes" ``` – Supritam Jun 06 '22 at 10:00

2 Answers2

1

Seems like you're trying to get a property using a slice accessor, which doesn't work in Go. You'd need to write a function for each property. Here's an example with the brands:

func getUniqueBrands(v []Car) []string {
    var combined []string
    tempMap := make(map[string]bool)

    for _, c := range v {
        if _, p := tempMap[c.brand]; !p {
            tempMap[c.brand] = true
            combined = append(combined, c.brand)
        }
    }
    return combined
}

Also, note the for loop being used to get the value of Car here. Go's range can be used to iterate over just indices or both indices and values. The index is discarded by assigning to _.

I would recommend re-using this code with an added switch-case block to get the result you want. If you need to return multiple types, use interface{} and type assertion.

Emily
  • 98
  • 1
  • 7
  • I read your codes, it seems it also prevent duplication in `combined` am I right? – Someone Special Jun 06 '22 at 10:01
  • Yep, it does. See the bold bit at the bottom, it's a good place to go with this code. – Emily Jun 06 '22 at 10:03
  • 1
    I'd recommend changing `map[string]bool` to `map[string]struct{}`, as `struct{}` defined as a zero-byte type, and thus more efficient than `bool`. I'd also either use `map[string]struct{}{}` instead of make, or use a size to pre-allocate the map to avoid reallocation at runtime (e.g. `make(map[string]struct{}, len(v))` -> ensure the map is big enough in case all `v` values are unique. – Elias Van Ootegem Jun 07 '22 at 10:34
0

Maybe you could marshal your struct into json data then convert it to a map. Example code:

package main

import (
    "encoding/json"
    "fmt"
)

type RandomStruct struct {
    FieldA string
    FieldB int
    FieldC string
    RandomFieldD bool
    RandomFieldE interface{}
}

func main() {
    fieldName := "FieldC"
    randomStruct := RandomStruct{
        FieldA:       "a",
        FieldB:       5,
        FieldC:       "c",
        RandomFieldD: false,
        RandomFieldE: map[string]string{"innerFieldA": "??"},
    }
    randomStructs := make([]RandomStruct, 0)
    randomStructs = append(randomStructs, randomStruct, randomStruct, randomStruct)
    res := FetchRandomFieldAndConcat(randomStructs, fieldName)
    fmt.Println(res)
}

func FetchRandomFieldAndConcat(randomStructs []RandomStruct, fieldName string) []interface{} {
    res := make([]interface{}, 0)
    for _, randomStruct := range randomStructs {
        jsonData, _ := json.Marshal(randomStruct)
        jsonMap := make(map[string]interface{})
        err := json.Unmarshal(jsonData, &jsonMap)
        if err != nil {
            fmt.Println(err)
            // panic(err)
        }
        value, exists := jsonMap[fieldName]
        if exists {
            res = append(res, value)
        }
    }
    return res
}
Corrado
  • 23
  • 4
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 06 '22 at 12:26