go-jet查询结果仅能映射匿名结构体,命名结构体失效排查
go-jet查询结果无法映射到局部命名结构体问题
使用go-jet v2.11.1时遇到异常:查询结果可正确填充匿名结构体,但无法映射到函数内部定义的命名结构体,即使两者字段结构、标签完全一致。命名结构体仅值类型字段(如Email、Id)保留初始空值,指针类型字段无数据,而匿名结构体能完整获取所有查询结果。
代码示例
package api import ( "fmt" "reflect" "github.com/Napam/apato/internal/api/database" . "github.com/Napam/apato/internal/generated/apato/app/table" "github.com/Napam/apato/internal/utils/auth" "github.com/Napam/apato/internal/utils/logging" . "github.com/go-jet/jet/v2/postgres" "github.com/labstack/echo/v4" "github.com/rs/zerolog" ) func (h *Handlers) GetAuthUser(c echo.Context) error { logger := logging.GetRequestLogger(c) logger.Info().Msg("Start get auth user") userId, err := auth.GetRequestUserId(c) if err != nil { return &echo.HTTPError{ Code: 401, Message: "Unauthorized", } } logger.Info().Str("userId", userId).Msg("Retrieved user id from request context") selectStatment := Users.SELECT( Users.FirstName.AS("firstName"), Users.LastName.AS("lastName"), Users.Email.AS("email"), Users.ID.AS("id"), Users.ProviderName.AS("providerName"), ).WHERE( Users.ID.EQ(String(userId)), ) logger.Debug().Func(func(e *zerolog.Event) { e.Str("SQL", selectStatment.DebugSql()) }).Msg("SQL to get user") type TEST struct { Email string `json:"email"` FirstName *string `json:"firstName,omitempty"` Id string `json:"id"` LastName *string `json:"lastName,omitempty"` ProviderName *string `json:"providerName,omitempty"` } var DBUser = TEST{ Email: "", FirstName: nil, Id: "", LastName: nil, ProviderName: nil, } var DBUser2 struct { Email string `json:"email"` FirstName *string `json:"firstName,omitempty"` Id string `json:"id"` LastName *string `json:"lastName,omitempty"` ProviderName *string `json:"providerName,omitempty"` } // Use reflection to inspect the fields of DBUser val := reflect.ValueOf(DBUser) for i := 0; i < val.NumField(); i++ { field := val.Type().Field(i) value := val.Field(i).Interface() logger.Debug().Str("Field", field.Name).Interface("Value", value).Interface("Tag", field.Tag).Msg("DBUser field value") } // Use reflection to inspect the fields of DBUser2 val = reflect.ValueOf(DBUser2) for i := 0; i < val.NumField(); i++ { field := val.Type().Field(i) value := val.Field(i).Interface() logger.Debug().Str("Field", field.Name).Interface("Value", value).Interface("Tag", field.Tag).Msg("DBUser2 field value") } err = selectStatment.Query(database.DB, &DBUser) if err != nil { return fmt.Errorf("Error querying database: %w", err) } err = selectStatment.Query(database.DB, &DBUser2) if err != nil { return fmt.Errorf("Error querying database: %w", err) } logger.Debug().Interface("DBUser", DBUser).Msg("Retrieved user from database") logger.Debug().Interface("DBUser2", DBUser2).Msg("Retrieved user from database") return c.JSON(200, map[string]any{ "DBUser": DBUser, "DBUser2": DBUser2, }) }
实际输出
{ DBUser: { email: '', id: '' }, DBUser2: { email: 'xxxxx@xxxxxxx.com', firstName: 'Naphat', id: '123123123', lastName: 'Amundsen', providerName: 'google' } }
环境信息
- Golang: go1.23.1 darwin/arm64
- 操作系统: MacOS Sonoma 14.6.1
- 数据库: Postgres 16.4
- 驱动: github.com/lib/pg v1.10.9
- go-jet版本: v2.11.1
问题成因
核心原因是go-jet的反射映射机制无法正确处理函数内部定义的局部命名结构体。go-jet内部会缓存结构体的字段映射关系,而局部类型的包路径(PkgPath)为空,导致其无法被正确识别和缓存;此外,Go反射对局部类型的处理逻辑与包级类型存在差异,进一步导致字段映射失败。
而匿名结构体虽然也是局部定义,但go-jet对其采用了不同的反射处理逻辑,绕过了类型缓存的限制,因此能正常映射。
解决方法
- 将结构体移至包级定义:把命名结构体从函数内部移到包级别(即使不导出,只要在包级声明即可):
// 包级定义结构体 type TEST struct { Email string `json:"email"` FirstName *string `json:"firstName,omitempty"` Id string `json:"id"` LastName *string `json:"lastName,omitempty"` ProviderName *string `json:"providerName,omitempty"` } func (h *Handlers) GetAuthUser(c echo.Context) error { // ... 原有代码 var DBUser TEST // 直接使用包级类型 // ... 原有代码 }
- 使用jet标签明确映射关系:建议使用go-jet官方推荐的
jet标签指定列名,避免自动转换的潜在问题:
type TEST struct { Email string `json:"email" jet:"email"` FirstName *string `json:"firstName,omitempty" jet:"firstName"` Id string `json:"id" jet:"id"` LastName *string `json:"lastName,omitempty" jet:"lastName"` ProviderName *string `json:"providerName,omitempty" jet:"providerName"` }
内容的提问来源于stack exchange,提问作者Naphat Amundsen
相关产品推荐
相关产品推荐

