9

In Go, I would like to do something like this. I have a big object with many structs (using Google's protobuf). here is a contrived example:

person.name = "testing"
person.address.street = "123 test st"
person.address.city = "tester"
person.address.zip = 90210
person.billing.address.same = true

I would like to be able to dynamically reference things. for example:

key := "person.address.zip"
fmt.Println("the value of key: " + key) // would like to get 90210
key := "person.address.city"
fmt.Println("the value of key: " + key) // would like to get "tester"

Is this possible in Go? if so, how could I do that? essentially, I'm creating a report which only contains a subset of the object and I want to be able to create a mapping file where the user can map keys/values together and my program will output the value. I have this working in python, but wanted to try using Go :)

4
  • Yes, it is possible. It provides you package reflect. Disadvantage of reflect is usually big amount of work. On the other hand - without reflect can't be written such perfect packages like html/template or text/template where style of writting is like in interpreted language (like python) although go is compiled. Commented Aug 13, 2016 at 16:55
  • and reflect is slow, should be last resort. Commented Aug 13, 2016 at 17:26
  • 4
    If you really really need something like this, you should use (nested) maps, not hierarchies of struct values. Commented Aug 13, 2016 at 18:51
  • Nested maps like map[string]interface{} are your friends Commented Aug 14, 2016 at 2:39

2 Answers 2

8

You may use func (v Value) FieldByName(name string) Value from reflect package:

FieldByName returns the struct field with the given name. It returns the zero Value if no field was found. It panics if v's Kind is not struct.

Like this working sample code:

package main

import "fmt"
import "reflect"

func main() {
    person := Person{}
    person.name = "testing"
    person.address.street = "123 test st"
    person.address.city = "tester"
    person.address.zip = 90210
    person.billing.address.same = true

    v := reflect.ValueOf(person)
    f := v.FieldByName("address")
    key := f.FieldByName("zip")
    fmt.Println(key)                   // 90210
    fmt.Println(f.FieldByName("city")) // tester    
}

type Person struct {
    name    string
    address Address
    billing Billing
}
type Billing struct {
    address Address
}
type Address struct {
    street, city string
    zip          int
    same         bool
}

output:

90210
tester

And for your special case, you may use fmt.Println(field(person, "person.address.zip")), like this working sample code (just for demonstration):

package main

import "fmt"
import "reflect"
import "strings"

func field(t interface{}, key string) reflect.Value {
    strs := strings.Split(key, ".")
    v := reflect.ValueOf(t)
    for _, s := range strs[1:] {
        v = v.FieldByName(s)
    }
    return v
}
func main() {
    person := Person{}
    person.name = "testing"
    person.address.street = "123 test st"
    person.address.city = "tester"
    person.address.zip = 90210
    person.billing.address.same = true

    fmt.Println(field(person, "person.address.zip"))  //90210
    fmt.Println(field(person, "person.address.city")) //tester
}

type Person struct {
    name    string
    address Address
    billing Billing
}
type Billing struct {
    address Address
}
type Address struct {
    street, city string
    zip          int
    same         bool
}

output:

90210
tester
Sign up to request clarification or add additional context in comments.

Comments

1

I'm not familiar with protobuf's internals or if it provides any means to do that.

But, (1) if you like to read the value in the way you described - by chaining fields dynamically and (2) you want to read it more than one time; I would just serialize it into json and use this package. It's very fast and gives you (almost) the same semantic you desire:

// assuming your object got marshaled to this for example
json := `{"name":{"first":"Janet","last":"Prichard"},"age":47}`
value := gjson.Get(json, "name.last")
println(value.String())

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.