Hi, @IBMRob,
I don’t follow exactly what you’re looking for; do you want to pass in a value and then a “path” to select from the value like "Spec.Resources.Server.Requests.Cpu"
and get the resulting field? If so, I believe you cannot do that with any built-in language features and you’d probably need to do that with the reflect
package. I can think of some alternatives that might work better (or worse) for you, though:
A simple (but potentially wasteful, depending on your use case) solution might be to “json
ify” the data:
func ResolveValue(cr *productv1beta1.Server, CrKey string) (string, error) {
bs, err := json.Marshal(cr)
if err != nil {
return "", err
}
var m map[string]interface{}
if err := json.Unmarshal(bs, &m); err != nil {
return "", err
}
path := strings.Split(CrKey, ".")
for _, hop := range path[:len(path)-1] {
var ok bool
if m, ok = m[hop].(map[string]interface{}); !ok {
return "", fmt.Errorf("invalid key %q")
}
}
return fmt.Sprint(m[path[len(path)-1]]), nil
}
If you have many CrKey
s to retrieve from one piece of data, It might not actually be a bad idea to change the signature of this function to accept multiple CrKey
s and return a slice of results. If you only need to select a few CrKey
fields and/or have many actual *productv1beta1.Server
values, this would be inefficient.
If that’s the case, I’d ask if you have control over this productv1beta1.Server
type. If you do, I’d recommend some code generation to make that type implement an interface like this:
type ValueResolver interface {
ResolveValue(key string) (interface{}, error)
}
Then in .../productv1beta1/server.go
or wherever:
package productv1beta1
// ...
type Server struct {
// ...
Resources SomeType
// ...
}
var serverFields = map[string]func(s *Server) interface{}{
// ...
"Resources": func(s *Server) interface{} { return s.Resources },
// ...
}
func (s *Server) ResolveValue(key string) (interface{}, error) {
f, ok := serverFields[key]
if !ok {
return nil, fmt.Errorf("invalid field: %q", key)
}
return f(s), nil
}
Then change your ResolveValue function to do something like this:
func ResolveValue(vr ValueResolver, key string) (string, error) {
var ok bool
path := strings.Split(key, ".")
for _, hop := range path[:len(path)-1] {
if vr, ok = vr.ResolveValue(hop).(ValueResolver); !ok {
return "", fmt.Errorf("cannot resolve %q from %T", hop, vr)
}
}
v, err := vr.ResolveValue(path[len(path)-1])
if err != nil {
return "", err
}
return fmt.Sprint(v), nil
}
I suspect that will be more efficient than either the reflect
- or the encoding/json
-based solutions but at the cost of more complexity in your code.
You might also have luck with the text/template
package by treating your CrKey
as a template (e.g. "{{.Spec.Resources.Server.Requests.Cpu}}"
and then your *productv1beta1.Server
as a model to execute against. Not sure how that’d perform against reflection, json, or the ValueResolver
solution above.
If you’re only dealing with struct fields in *productv1beta1.Server
, you could also leverage the unsafe
package with the reflect
package to make it really fast.