Expression evaluation in Golang

Background:
I have various models for DB Tables which basically consists of structs having fields which map to Table columns.

When I am required to do a query on particular table so I do the following,

tableStruct := []models.ModelName{} // Assuming ModelName is a model witch struct of the format of some table column.
models.DB.Where("column_name = ?", "abc").All(&tableName) // Ignore the syntax its of soda pop
// I get all the data from the table corresponding to this query in tableStruct

Now the problem:
If I get the modelName from some query or api request, then how can I evaluate this expression.

modelName := "xyzModel"
tableStruct := []models. `modelName` {} // How can I evaluate this expression?

In Python it’s simply:

tableStruct = eval(f"models.{modelName}"+"{}")

How can I achieve this functionality in Golang. If not possible then please suggest some other way for the above problem.

Thanks

Hi @Aniket_Pathak,

As an interpreted language, Python allows evaluating a string as code at runtime (which BTW can be dangerous to do if the string is received from an unknown origin). Go does not have that convenience.

Instead, you can maintain a map from names to models, e.g.

var models map[string]models.ModelName

and register each model by name

models["xyzModel"] = <insert ModelName instance here>

and use it like so:

modelName := "xyzModel"
tableStruct := models[modelName]
1 Like

Hi @christophberger, thanks for the solution.

I still have some issues regarding the implementation,

Here, models.ModelName will have different type for each Model lets say Model1 and Model2. So, mapping will be an issue for all the models.

var modelsMap map[string]models.Model1
modelsMap["xyzModel"] = []models.Model1{}

Then,

modelsMap["abcModel"] = []models.Model2{}

This gives,

cannot use (models.Model2 literal) (value of type models.Model2) as models.Model1 value in assignment

Any idea how to overcome this issue?

If I use interface in the map definition then I will need to typecast it back to the required struct which will again require expression evaluation.

Thanks again!

Can you provide more context around how you’re going to use this? Python is a dynamic programming language where code doesn’t need to track object’s types as long as they follow the same protocols (have the same attributes, if they’re callable, indexable, etc.). Go is not dynamic and requires that expressions produce statically-typed values. Because of this major difference, there are some patterns that work in Python but cannot work in Go and need to be refactored. If your models don’t have any common methods (or, as models, perhaps have no methods), then you have to store them as interface{}s.

However, It looks like you’re not storing model instances, but instead their types so you can instantiate them. You could do something similar in Go, but you will need to keep a map of your types manually, like Christoph said. There’s no way to lookup a type or a function from a package, even with the reflect package.

var modelTypes = map[string]reflect.Type{
    reflect.TypeOf(Model1{}),
    reflect.TypeOf(Model2{}),
    // ...
}

func query(modelName string) (interface{}, error) {
    modelType, ok := modelTypes[modelName]
    if !ok {
        return nil, errBadType
    }
    sliceType := reflect.SliceOf(modelType)
    slicePtr := reflect.New(sliceType) // pointer to ensure it's addressable
    slicePtr.Elem().Set(reflect.MakeSlice(sliceType, 0, 4)) // arbitrary initial capacity
    models.DB.Where("column_name = ?", "abc").All(slicePtr.Interface())
    // Because types must be static, and Go doesn't yet support
    // generics, you're stuck with an interface{} that holds the
    // proper []ModelType inside.  The caller will have to
    // type assert the result to the proper type.
    return slicePtr.Elem().Interface(), nil
}

We might be able to provide alternatives to having the caller know and convert the type if we see what you do with the result.

4 Likes

Thanks @skillian, this helped.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.