How do I set a dynamic struct field in Go?

huangapple go评论67阅读模式
英文:

How do I set a dynamic struct field in Go?

问题

我正在使用Go语言将JSON解组为map[string]interface{},并使用接口的混合字符串、浮点数和切片值来填充PlaceNode结构体的字段值。

我需要类似于"默认值"的东西,因为并不是所有的JSON对象都具有所有的结构字段。如果从其他语言背景来看,如果结构体是可索引的,我会习惯于像在JavaScript中设置n Placenode变量的值一样做一些操作(例如,就像在JavaScript中的selfthis关键字一样)。

n[key] = value;

然而,在Go语言中,我有一个在PlaceNode结构体上的方法来读取interface{},使用reflect并在字段可以设置时可选地分配一个值。我的interfaces{}并不实现所有的值,所以我在直接解组到结构体时遇到了问题。

显然,没有一个字段通过了这个s.CanSet()检查。所以我一定是做错了。

在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
}

data参数map[string]interface{}的接口也是一个map[string]interface{},看起来像这样:

{
    "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
    }
}
英文:

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
   }
 }

答案1

得分: 16

当你进行以下调用时:

st := reflect.ValueOf(n)

ValueOf 接收到的是 PlaceNode 结构体的一个副本。因此,对 st 所做的任何更改都不会在 n 中看到。因此,该包将此类情况视为不可寻址的值。如果你想要一个代表 nreflect.Value,可以尝试使用类似以下的代码:

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

现在,st 直接与 n 一起工作,而不是一个副本,并且是可寻址的。现在,你应该能够在 st 及其字段上使用各种 Set* 方法。

英文:

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.

huangapple
  • 本文由 发表于 2014年8月30日 09:20:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/25578137.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定