139

Given the scenario where you have a function which accepts t interface{}. If it is determined that the t is a slice, how do I range over that slice?

func main() {
    data := []string{"one","two","three"}
    test(data)
    moredata := []int{1,2,3}
    test(data)
}

func test(t interface{}) {
    switch reflect.TypeOf(t).Kind() {
    case reflect.Slice:
        // how do I iterate here?
        for _,value := range t {
            fmt.Println(value)
        }
    }
}

Go Playground Example: http://play.golang.org/p/DNldAlNShB

Inanc Gumus
  • 25,195
  • 9
  • 85
  • 101
Owen Allen
  • 11,348
  • 9
  • 51
  • 63
  • Why not have the function take a []interface{} instead? Are you trying to handle multiple complex types? – Jeremy Wall Dec 25 '12 at 16:40
  • 3
    Yes, it's for a templating system so interface{} could be a map, struct, slice, or array. In the real code there are many more case statements, but I removed them from the post to make the problem more concise. – Owen Allen Dec 26 '12 at 05:23
  • 3
    Jeremy, a []string is not a subtype of []interface{}, so you can't call a func([]interface{}) function with a []string or []int, etc. It would be nice to have a feature in Go to have a type meaning "slice of something", where you can then iterate over the elements as interface{}, but unfortunately you need reflection for that. – Bjarke Ebert Jun 03 '16 at 12:34
  • @JeremyWall You can't use []interface{} to act as slice. You can refer [here](https://github.com/golang/go/wiki/InterfaceSlice). – firelyu Feb 10 '17 at 03:21

4 Answers4

183

Well I used reflect.ValueOf and then if it is a slice you can call Len() and Index() on the value to get the len of the slice and element at an index. I don't think you will be able to use the range operate to do this.

package main

import "fmt"
import "reflect"

func main() {
    data := []string{"one","two","three"}
    test(data)
    moredata := []int{1,2,3}
    test(moredata)
} 

func test(t interface{}) {
    switch reflect.TypeOf(t).Kind() {
    case reflect.Slice:
        s := reflect.ValueOf(t)

        for i := 0; i < s.Len(); i++ {
            fmt.Println(s.Index(i))
        }
    }
}

Go Playground Example: http://play.golang.org/p/gQhCTiwPAq

masebase
  • 4,915
  • 3
  • 24
  • 20
  • 34
    Works great, thank you. The only thing to add would be that `s.Index(i)` returns a `reflect.Value` so in my case I needed `s.Index(i).Interface()` to reference the actual value. – Owen Allen Dec 25 '12 at 06:23
  • 1
    What would you do if you had a pointer to a slice in `interface{}`? e.g. `moredata := &[]int{1,2,3}` – Ryan Walls Jun 09 '16 at 16:52
  • 2
    Answering my question: If you have a pointer to a slice instead of a slice, you'll need to use `Elem()` to get the underlying value. e.g. `reflect.TypeOf(reflect.ValueOf(t).Elem().Interface()).Kind()` – Ryan Walls Jun 09 '16 at 20:03
  • what if I want to get value of interface field from slice of interface without any struct. I have tried this solution but it is limited to get interface reference only from slice not the actual values of fields. – Amandeep kaur Sep 27 '17 at 09:54
  • woh many thanks, that trick saved me a lot of time (and headache!) – Nicolas Garnier Jan 07 '20 at 10:22
  • How to do set value after get value by `s.Index(i).Interface()` – Ertuğrul Altınboğa Oct 19 '22 at 07:15
37

You don't need to use reflection if you know which types to expect. You can use a type switch, like this:

package main

import "fmt"

func main() {
    loop([]string{"one", "two", "three"})
    loop([]int{1, 2, 3})
}

func loop(t interface{}) {
    switch t := t.(type) {
    case []string:
        for _, value := range t {
            fmt.Println(value)
        }
    case []int:
        for _, value := range t {
            fmt.Println(value)
        }
    }
}

Check out the code on the playground.

Inanc Gumus
  • 25,195
  • 9
  • 85
  • 101
  • This will not work over an array though, only over a slice – user1028741 Jan 15 '20 at 11:16
  • @user1028741 No, the code works for every type including arrays + You can always take a slice from an array `array[:]`. – Inanc Gumus Feb 11 '20 at 22:03
  • 2
    But the type of an array will be with the actual capacity of it. [3]int is not of the same type as [2]int or []int. Therefore, if the type of 't' is actually an array the relevant case will not apply. – user1028741 Feb 20 '20 at 13:22
  • 2
    What I mean is your example will not work on arrays. I changed it here: https://play.golang.org/p/DAsg_0aXz-r – user1028741 Mar 01 '20 at 14:51
  • You can't easily make it work, if you don't know the type of your parameter in the first place. In order to use your trick (array[:]), you'll have to use reflection to decide that it is an array, then cast to array - then generate a slice of it. That's why I said it works on a slice, not on an array. Clearly with enough effort you can produce a slice out of the array... – user1028741 Mar 03 '20 at 10:29
  • sad it doesn't work with `fallthough`, nor with specifying more than one value in a `case`; otherwise, it'd be absolutely perfect. https://play.golang.org/p/swjPprq8KRi – cnst Jul 15 '20 at 19:26
  • @cnst That'd work in a weakly-typed lang but not in Go, yeah. – Inanc Gumus Jul 15 '20 at 19:40
  • @InancGumus but a fall-through wouldn't be weakly-typed at all; it's likely that the compiler just doesn't support it yet; a fall-through-like scenario can already be accomplished by defining a new interface and avoiding having to have the switch on type, but, in that case, there'll be no type-checking (outside of runtime) that the implementations still implement the same interface that you define locally; realistically, I see no good reason for the `./prog.go:16:3: cannot fallthrough in type switch` error for https://play.golang.org/p/swjPprq8KRi. – cnst Jul 16 '20 at 04:49
  • @cnst In Go, you can't morph types without explicitly typing them. That's why fallthrough doesn't work (and shouldn't) in a type switch. It's all about the very strong type system and Go's explicitness that expects you to type everything clearly. – Inanc Gumus Jul 16 '20 at 06:51
4

Expanding on the answer provided by masebase, you could generalize the iteration on an interface{} slice with a function like this:

func forEachValue(ifaceSlice interface{}, f func(i int, val interface{})) {
    v := reflect.ValueOf(ifaceSlice)
    if v.Kind() == reflect.Ptr {
        v = v.Elem()
    }
    if v.Kind() != reflect.Slice {
        panic(fmt.Errorf("forEachValue: expected slice type, found %q", v.Kind().String()))
    }

    for i := 0; i < v.Len(); i++ {
        val := v.Index(i).Interface()
        f(i, val)
    }
}

Then, you use it like this:

func main() {
    data := []string{"one","two","three"}
    test(data)
    moredata := []int{1,2,3}
    test(data)
}

func test(sliceIface interface{}) {
    forEachValue(sliceIface, func(i int, value interface{}) {
      fmt.Println(value)
    }
}
tothemario
  • 5,851
  • 3
  • 44
  • 39
3

there is one exception from the way interface{} behaves, @Jeremy Wall gave already pointer. if the passed data is defined as []interface{} initially.

package main

import (
    "fmt"
)

type interfaceSliceType []interface{}

var interfaceAsSlice interfaceSliceType

func main() {
    loop(append(interfaceAsSlice, 1, 2, 3))
    loop(append(interfaceAsSlice, "1", "2", "3"))
    // or
    loop([]interface{}{[]string{"1"}, []string{"2"}, []string{"3"}})
    fmt.Println("------------------")


    // and of course one such slice can hold any type
    loop(interfaceSliceType{"string", 999, map[int]string{3: "three"}})
}

func loop(slice []interface{}) {
    for _, elem := range slice {
        switch elemTyped := elem.(type) {
        case int:
            fmt.Println("int:", elemTyped)
        case string:
            fmt.Println("string:", elemTyped)
        case []string:
            fmt.Println("[]string:", elemTyped)
        case interface{}:
            fmt.Println("map:", elemTyped)
        }
    }
}

output:

int: 1
int: 2
int: 3
string: 1
string: 2
string: 3
[]string: [1]
[]string: [2]
[]string: [3]
------------------
string: string
int: 999
map: map[3:three]

try it out

Matus Kral
  • 39
  • 4