有没有一种方法可以从字符串创建一个结构体的实例?

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

is there a way to create an instance of a struct from a string?

问题

给定一个结构体:

type MyStruct struct {
    A int
    B int
}

以及一个包含结构体名称的字符串:

a := "MyStruct"

或者

a := "mypkg.MyStruct"

如何通过字符串名称而不是结构体本身创建结构体的实例?想法是我会创建一个应用程序,将所有的结构体链接到二进制文件中,但是通过字符串来创建运行时实例。(有点类似元元编程)

英文:

Given a struct:

type MyStruct struct {
    A int
    B int
}

and a string with the struct's name

a := "MyStruct"

or

a := "mypkg.MyStruct"

How do I create an instance of my struct from the string name rather than the struct? The idea is that I would create an application with all of the structures linked into the binary but create the runtime instances from the strings. (sort of a meta-meta)

答案1

得分: 86

在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()
    // 如果需要的话,在这里填充字段
    return v.Interface()
}

然后,你可以这样创建类型的实例:

instance := makeInstance("MyString")
英文:

There is no central registry of types in Go, so what you ask is impossible in the general case.

You could build up your own registry by hand to support such a feature using a map from strings to reflect.Type values corresponding to each type. For instance:

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

You can then create instances of the types like so:

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

答案2

得分: 22

Go运行时不公开程序中内置类型的列表。这是有原因的:你不需要能够构建所有可用类型,而只需要构建一个子集。

你可以使用映射自己构建这个子集。你可以使用reflect包从reflect.Type创建一个实例。

我的解决方案(在Go Playground上查看)使用了有类型的空指针(而不是空值),以减少构建映射时的分配大小(与@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"))
}

希望这可以帮助到你!

英文:

The Go runtime doesn't exposes a list of types built in the program. And there is a reason: you never have to be able to build all types availables but instead just a subset.

You can build yourself this subset using a map. And you can use the reflect package to create an instance from a reflect.Type.

My solution (see on Go Playground) uses typed nil pointers (instead of empty values) to reduce size of allocations when building the map (compared to @james-henstridge solution).

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

答案3

得分: 11

你可以创建一个名称 -> 结构体 "template" 的映射。

从映射中获取值时,你会得到该值的副本,映射实际上充当了值的工厂。

请注意,映射中的值是唯一的。
为了实际处理结构体,你需要断言其类型或使用一些基于反射的处理器(例如:从映射中获取结构体,然后将其解码为 JSON)。

以下是一个简单的示例,其中包含一个原始形式的结构体和一个预填充的结构体。
请注意对 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)
}

示例链接

英文:

You can create a map of name -> struct "template"

When grabbing a value from a map, you get a copy of the value, the map effectively acts as a factory for your values.

Notice that the values from the map are unique.
In order to actually do something with the struct, you'll need to either assert its type or use some reflections based processor (ie: get struct from map, then json decode in to the struct)

Here's a simple Example with one struct in raw form and one pre-filled in.
Notice the type assertion on foowv1, that's so I can actually set the value.

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

huangapple
  • 本文由 发表于 2014年4月12日 21:17:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/23030884.html
匿名

发表评论

匿名网友

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

确定