在运行时动态创建结构体 - 可能吗?

3

在Go语言中,是否可能通过编程方式创建结构体类型(即不在编译后的源代码中定义)?

我们有一个特定的用例,其中类型将通过用户定义的元数据创建(因此预先未知模式/类型),并且每个客户的类型都不同。然后,我们需要自动生成这些类型的REST服务,并将它们持久化到NoSQL后端。我们还需要为每个字段定义不同的动态验证器(例如:强制、正则表达式、最大/最小值、对另一类型实例的引用等)。

我想知道Go语言中是否存在类似的功能?

编辑1:

例如:

从前端以JSON格式传输

For customer 1:
{
"id":1,
"patientid":1,
"name":"test1",
"height":"160",
"weight":"70",
"temp":"98"
}

For customer 2:
{
"id":2,
"patientid":1,
"name":"test1",
"height":"160",
"weight":"70"
}

For customer 3

may be different new fields will add

Backend

// For One customer need to have these fields 

type Vitalsigns struct {
    ID                int64  `datastore:"-"`
    PatientID         int64  `json:"patientid,omitempty"`
    Name              string `json:"name,omitempty"`
    Height            string `json:"height,omitempty"`
    Weight            string `json:"weight,omitempty"`
    Temp              string `json:"temp,omitempty"`
}



// Another need to have these fields

type Vitalsigns struct {
    ID                int64  `datastore:"-"`
    PatientID         int64  `json:"patientid,omitempty"`
    Name              string `json:"name,omitempty"`
    Height            string `json:"height,omitempty"`
    Weight            string `json:"weight,omitempty"`
}


//CreateVitalsignsHandler is to create vitals for a patient
func CreateVitalsignsHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {

    //Creating the Vitalsigns
    kinVitalsigns := &Vitalsigns{}
    ctx := appengine.NewContext(r)
    if err := json.NewDecoder(r.Body).Decode(kinVitalsigns); err != nil {
        RespondErr(w, r, http.StatusInternalServerError, err.Error())
        return
    }
    //Getting namespace
    namespace := ps.ByName("namespace")
    ctx, err := appengine.Namespace(ctx, namespace)
    if err != nil {
        log.Infof(ctx, "Namespace error from CreateVitalsignsHandler")
        RespondErr(w, r, http.StatusInternalServerError, err.Error())
        return
    }
    //Geting the patientID
    pID, err := strconv.Atoi(ps.ByName("id"))
    if err != nil {
        log.Infof(ctx, "Srting to Int64 conversion error from CreateVitalsignsHandler")
        RespondErr(w, r, http.StatusInternalServerError, err.Error())
        return
    }
    patientID := int64(pID)
    kinVitalsigns.PatientID = patientID

    //Generating the key
    vitalsignsKey := datastore.NewIncompleteKey(ctx, "Vitalsigns", nil)

    //Inserting the data to the datastore
    vk, err := datastore.Put(ctx, vitalsignsKey, kinVitalsigns)
    if err != nil {
        log.Infof(ctx, "Entity creation was failed from CreateVitalsignsHandler")
        RespondErr(w, r, http.StatusInternalServerError, err.Error())
        return
    }
    kinVitalsigns.ID = vk.IntID()
    message := "Vitalsigns created successfully!! "
    Respond(w, r, http.StatusOK, SuccessResponse{kinVitalsigns.ID, 0, "", message})
    return
}
1个回答

12

编辑:您的编辑显示您想处理动态对象以从Google Datastore中获取/存储。对于此操作,完全不需要在运行时创建结构类型,您可以使用本答案中介绍的动态映射:如何在Google应用程序引擎数据存储中使用Go具有动态属性

原始答案如下。


请注意,如果在编译时已知类型,则最佳/最有效的方法是创建类型并编译它们,这样一切都将是“静态”的。您可以手动创建类型,也可以使用go generate自动化该过程。

还要注意,您可能不一定需要结构类型来模拟动态对象,很多情况下,映射可能足够。

如果在编译时未知类型,并且必须使用结构类型,请继续阅读。

是的,可以使用Go的反射,在运行时创建“动态”结构类型,特别是使用reflect.StructOf()函数。

让我们看一个简单的例子,在运行时创建一个具有Name stringAge int字段的结构类型:

t := reflect.StructOf([]reflect.StructField{
    {
        Name: "Name",
        Type: reflect.TypeOf(""), // string
    },
    {
        Name: "Age",
        Type: reflect.TypeOf(0), // int
    },
})

fmt.Println(t)

v := reflect.New(t)
fmt.Printf("%+v\n", v)
v.Elem().FieldByName("Name").Set(reflect.ValueOf("Bob"))
v.Elem().FieldByName("Age").Set(reflect.ValueOf(12))

fmt.Printf("%+v\n", v)

这是输出结果(在Go Playground上进行测试):
struct { Name string; Age int }
&{Name: Age:0}
&{Name:Bob Age:12}

如果您想定义验证规则,可以使用第三方库,例如github.com/go-validator/validator。该软件包使用结构标记来指定验证规则,您也可以使用反射来指定结构标记。
例如,如果您想指定Name必须至少为3个字符且最多为40个字符,并且只能包含英文字母,并且Age的有效范围是6..100(包括两端),则如下所示:
t := reflect.StructOf([]reflect.StructField{
    {
        Name: "Name",
        Type: reflect.TypeOf(""), // string
        Tag:  reflect.StructTag(`validate:"min=3,max=40,regexp=^[a-zA-Z]*$"`),
    },
    {
        Name: "Age",
        Type: reflect.TypeOf(0), // int
        Tag:  reflect.StructTag(`validate:"min=6,max=100"`),
    },
})

如果输出这种类型(由我包装),则会得到以下结果(请在Go Playground上尝试):

struct { Name string "validate:\"min=3,max=40,regexp=^[a-zA-Z]*$\"";
    Age int "validate:\"min=6,max=100\"" }

创建此结构体的实例后,您可以使用validator.Validate()函数对其进行验证,例如:

v := reflect.New(t)
v.Elem().FieldByName("Name").Set(reflect.ValueOf("Bob"))
v.Elem().FieldByName("Age").Set(reflect.ValueOf(12))

if errs := validator.Validate(v.Elem().Interface()); errs != nil {
    // values not valid, deal with errors here
}

我已经更新了我的答案,上面的代码有点令人困惑。我将从前端获取动态JSON,我该如何将其转换为结构体以插入数据存储器。 - undefined
虽然这是一个对于提出的问题来说很好的解决方案,但我非常强烈不建议使用它。这似乎是一个难以管理、运行缓慢,并且最终很可能是毫无意义的噩梦。编译时结构体或映射几乎肯定是更好的解决方案。 - undefined
@Adrian 有什么建议吗? - undefined
2
@Adrian 是的,这正是我在第一段中建议的,"rest" 是为了回答问题。 - undefined
是的,再次强烈建议“真的,不要这样做,尽管它是可能的!” - undefined
有时候你必须这样做。例如,我有一个使用案例,其中数据结构是从JSON模式在运行时创建的,我发现使用在运行时创建的结构体(使用github.com/modern-go/reflect2创建新实例)比使用映射更高效。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接