有没有办法从字符串创建结构的实例?

时间:2014-04-12 13:17:18

标签: go

给出一个结构:

type MyStruct struct {
    A int
    B int
}

和一个结构名称为

的字符串
a := "MyStruct"

a := "mypkg.MyStruct"

如何从字符串名称而不是结构创建结构的实例?我的想法是,我将创建一个应用程序,其中所有结构都链接到二进制文件中,但是从字符串创建运行时实例。 (一种元元)

3 个答案:

答案 0 :(得分:63)

Go中没有类型的中央注册表,因此在一般情况下你所要求的是不可能的。

您可以手动构建自己的注册表,以使用从字符串到每种类型对应的reflect.Type值的映射来支持此类功能。例如:

var typeRegistry = make(map[string]reflect.Type)

func init() {
    myTypes := []interface{}{MyString{}}
    for _, v := range myTypes {
        // typeRegistry["MyString"] = reflect.TypeOf(MyString{})
        typeRegistry[fmt.Sprintf("%T", v)] = reflect.TypeOf(v)
    }
}

然后您可以创建类型的实例,如下所示:

func makeInstance(name string) interface{} {
    v := reflect.New(typeRegistry[name]).Elem()
    // Maybe fill in fields here if necessary
    return v.Interface()
}

答案 1 :(得分:8)

您可以创建名称地图 - > struct“template”

从地图中获取值时,您会获得该值的副本,该地图有效地充当您的值的工厂。

请注意,地图中的值是唯一的。 为了实际对结构做一些事情,你需要断言它的类型或使用一些基于反射的处理器(即:从map获取struct,然后json解码到struct)

这是一个简单的Example,其中一个原始形式的结构和一个预先填充的结构。 注意foowv1上的类型断言,这样我实际上可以设置值。

package main

import "fmt"

type foo struct {
    a int
}

var factory map[string]interface{} = map[string]interface{}{
    "foo":          foo{},
    "foo.with.val": foo{2},
}

func main() {
    foo1 := factory["foo"]
    foo2 := factory["foo"]
    fmt.Println("foo1", &foo1, foo1)
    fmt.Println("foo2", &foo2, foo2)

    foowv1 := factory["foo.with.val"].(foo)
    foowv1.a = 123
    foowv2 := factory["foo.with.val"]
    fmt.Println("foowv1", &foowv1, foowv1)
    fmt.Println("foowv2", &foowv2, foowv2)
}

答案 2 :(得分:5)

Go运行时不会公开程序中构建的类型列表。并且有一个原因:您永远不必能够构建所有可用类型,而只需构建一个子集。

您可以使用地图为自己构建此子集。您可以使用reflect包从reflect.Type创建实例。

我的解决方案(see on Go Playground)使用类型化的nil指针(而不是空值)来减少构建地图时的分配大小(与@james-henstridge解决方案相比)。

package main

import (
    "fmt"
    "reflect"
)

var typeRegistry = make(map[string]reflect.Type)

func registerType(typedNil interface{}) {
    t := reflect.TypeOf(typedNil).Elem()
    typeRegistry[t.PkgPath() + "." + t.Name()] = t
}

type MyString string
type myString string


func init() {
    registerType((*MyString)(nil))
    registerType((*myString)(nil))
    // ...
}

func makeInstance(name string) interface{} {
    return reflect.New(typeRegistry[name]).Elem().Interface()
}

func main() {
    for k := range typeRegistry {
        fmt.Println(k)
    }
    fmt.Printf("%T\n", makeInstance("main.MyString"))
    fmt.Printf("%T\n", makeInstance("main.myString"))
}