pzkpfw
pzkpfw

Reputation: 604

Iterating over a struct in Golang and print the value if set

First of all, apologies if this question is confused since I'm just trying out Go and have no idea what I'm doing. I have a struct composed of a variety of attributes of different types, example:

type foo struct {
    bar string
    baz int
    bez []string
(...)

Initially I wanted to iterate over all these attributes and print the value if it existed, but I realized you cannot range over a struct the same way you could, say, a list or map. So I've tried out a few tricks with no luck (like trying to iterate over a separate list of attributes), and I think it's better I just ask for help because I'm probably in over my head here.

The idea is that if I create a new instance of this struct, I'd like to be able to then only print values that are set:

obj := foo{"bar_string", 1}

Given that the string slice bez is not set in obj, I'd like to be able to do something like (pseudo):

for i in obj:
    print i

Giving:

"bar_string"
1

Ideally, not printing [] which I guess is the zero value for bez.

Am I approaching this whole thing wrong? The reason I'm not using a map is because I'd like the attributes to be different types, and I'd like future differing objects I'm working in to be organized into structs for clarity.

Upvotes: 1

Views: 7368

Answers (1)

blackgreen
blackgreen

Reputation: 44587

Go doesn't have builtin struct iteration. The for ... range statement is applicable only to:

all entries of an array, slice, string or map, or values received on a channel

or defined types with one of those underlying types (e.g. type Foo []int)

If you must iterate over a struct not known at compile time, you can use the reflect package. To know whether a field is set or not, you can compare it to its zero value. Otherwise there is no notion of set vs. unset in a Go struct. As explained by Volker in this comment:

[...] There simply are no "unset" struct fields. If you do not "set" them they just will be set to their zero value by the compiler/runtime and you cannot (really!) distinguish between you setting the value or the compiler.

type Foo struct {
  Bar string
  Baz int
  Quux []int
}

// x := Foo{"bar", 1, nil}
func printAny(x interface{}) {
    v := reflect.ValueOf(x)

    for i := 0; i < v.NumField(); i++ {
        field := v.Field(i)
        if !reflect.DeepEqual(field.Interface(), reflect.Zero(field.Type()).Interface()) {
            fmt.Println(field) 
            // bar
            // 1
        }
    }
}

...but it's slower and there are some gotchas, for example:

  • field.Interface() panics if the field is unexported
  • in the if clause you can't just use the comparison operator == because operands might be not comparable:
  • you have to make sure that the zero value for field types is what you expect

If your goal is to just print the struct, you can simply implement the Stringer interface, where you can do type-safe checks the way you want without reflect:

type Foo struct {
  Bar string
  Baz int
  Quux []int
}

func (f Foo) String() string {
    s := []string{f.Bar, strconv.Itoa(f.Baz)}
    if f.Quux != nil {
        s = append(s, fmt.Sprintf("%v", f.Quux))
    }
    return strings.Join(s, "\n")
}

func main() {
   fmt.Println(Foo{"bar", 1, nil}) 
   // bar
   // 1
}

A Go playground

Upvotes: 2

Related Questions