Golang dynamic variable reference
Asked Answered
C

2

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 :)

Chaussure answered 13/8, 2016 at 16:2 Comment(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.Bandbox
and reflect is slow, should be last resort.Rawdin
If you really really need something like this, you should use (nested) maps, not hierarchies of struct values.Prokofiev
Nested maps like map[string]interface{} are your friendsRecognition
I
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
Isolda answered 14/8, 2016 at 3:42 Comment(0)
T
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())
Teacake answered 14/8, 2016 at 11:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.