buley
buley

Reputation: 29208

How do I set a dynamic struct field in 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
   }
 }

Upvotes: 5

Views: 10052

Answers (1)

James Henstridge
James Henstridge

Reputation: 43899

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.

Upvotes: 16

Related Questions