Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I set a dynamic struct field in Go?

Tags:

go

I'm unmarshalling JSON in Go to a map[string]interface{} and using the interface's mixed string, float and slice values to populate field values of a PlaceNode struct.

I need something like "Default values" because not all JSON objects have all Struct fields. Coming from other language backgrounds, were Structs indexable I'd be used to doing something like this to set a value on the n Placenode variable (e.g. as if it were a self or this keyword in JavaScript).

n[key] = value;

Instead, I have a method on my PlaceNode struct to read the interface{}, use reflect and optionally assign a value if the field can be set. My interfaces{} don't implement all values and so I'm having trouble unmarshmaling directly into my struct.

Apparently none of the fields pass this s.CanSet() check. So I must be going about this wrong.

How do I set a dynamic struct field in Go?

func (n PlaceNode) New(data map[string]interface{}) PlaceNode {
    for key, val := range data {
        n.Id = key
        for k, v := range val.(map[string]interface{}) {
            f := reflect.ValueOf(v)
            st := reflect.ValueOf(n)
            if (st.Kind() == reflect.Struct) {
                s := st.FieldByName(k)
                if f.Kind() == reflect.String && true == s.CanSet()  {
                        s.SetString(f.String());
                } else if f.Kind() == reflect.Float64 && true == s.CanSet() {
                        s.SetFloat(f.Float());
                } else if f.Kind() == reflect.Slice && true == s.CanSet() {
                        s.Set(f.Slice(0, f.Len()));
                }
            }
        }
    }
    return n
}

The data argument map[string]interface{} has an interface that is also a map[string]interface{} and looks like this:

  {
   "XV.12A": {
    "Area": 1189.132667,
    "CensusBlock": 2032,
    "CensusBlockGroup": 2,
    "CensusCbsaFips": 40900,
    "CensusCountyFips": 61,
    "CensusMcdFips": 90160,
    "CensusMsaFips": 6922,
    "CensusPlaceFips": 3204,
    "CensusStateFips": 6,
    "CensusTract": 203,
    "CensusYear": 2010,
    "Radius": 19.455402434548,
    "RegionSize": 1189.132667
   }
 }
like image 291
buley Avatar asked Dec 20 '22 11:12

buley


1 Answers

When you make the following call:

st := reflect.ValueOf(n)

ValueOf is passed a copy of the PlaceNode struct. So any changes made to st would not be seen in n. For this reason, the package treats cases like this as non-addressable values. If you want a reflect.Value that represernts n, try using something like this:

st := reflect.ValueOf(&n).Elem()

Now st is working directly with n rather than a copy, and is addressable. You should now be able to use the various Set* methods on it and its fields.

like image 130
James Henstridge Avatar answered Jan 05 '23 12:01

James Henstridge