正确存储查询XORM中JSON数据需区分字段类型、结构体类型与JSON序列化行为;推荐用string+“json”标签并手动序列化,或用json.RawMessage/自定义类型实现自动编解码,查询须用MySQL JSON函数而非点语法。

要在XORM中正确存储和查询JSON数据,必须明确区分数据库字段类型、Go结构体字段类型、JSON序列化行为三者之间的关系,否则会出现存入乱码、查询为空或类型断言失败等问题。
定义支持JSON的结构体字段
使用 string 类型配合 xorm:"json" 标签是最直接可靠的方式。
第一步:在结构体中声明字段为 string 类型,并添加 xorm:"json" 标签:
ExtraData string `xorm:"json" json:"extra_data"`
第二步:确保数据库对应列类型为 JSON(MySQL 5.7+)或 TEXT(兼容旧版或 SQLite);若用 Sync2 自动建表,XORM 会根据 xorm:"json" 推断为 TEXT,【不会自动创建 MySQL 原生 JSON 类型】。
第三步:插入前将 Go 值序列化为 JSON 字符串再赋值,例如:
user.ExtraData, _ = json.Marshal(map[string]interface{}{"status": "active", "tags": []string{"dev", "test"}})
这一步不可省略——XORM 不会自动调用 json.Marshal,它只负责把字段值原样写入数据库。如果直接赋一个 map,会触发 string(map) 转换,存进去的是类似 map[0xc000123456:0xc000789abc] 的内存地址乱码。
用 struct 字段替代 string 实现自动编解码
方法一:嵌套自定义 struct 并实现 driver.Valuer 和 sql.Scanner
定义一个可 JSON 编解码的类型:
type JSONMap map[string]interface{}
为它实现两个接口:
func (j JSONMap) Value() (driver.Value, error) { return json.Marshal(j) }
func (j *JSONMap) Scan(value interface{}) error { b, ok := value.([]byte); if !ok { return errors.New("type assertion to []byte failed") }; return json.Unmarshal(b, j) }
然后在结构体中使用:
Extra JSONMap `xorm:"json" json:"extra"`
这样插入时直接赋值 user.Extra = JSONMap{"role": "admin"} 即可,XORM 会在写入前自动调用 Value(),读取时自动调用 Scan()。
方法二:直接使用 json.RawMessage(推荐用于未知结构 JSON)
Config json.RawMessage `xorm:"json" json:"config"`
它不解析内容,只做透传,避免中间转换开销和 panic 风险。赋值时用 json.RawMessage(data) 包装字节切片,适合配置类字段。
对 JSON 字段执行条件查询
MySQL 原生 JSON 类型支持 JSON_CONTAINS、JSON_EXTRACT 等函数,但 XORM 默认不提供封装,需手写 SQL 片段。
查询 JSON 数组中是否包含某个值(如 tags 包含 "prod"):
engine.Where("JSON_CONTAINS(extra_data, '\"prod\"', '$.tags')").Find(&users)
注意字符串中的双引号必须转义为 \",且路径表达式 '$.tags' 必须用单引号包裹,否则 MySQL 报语法错误。
查询 JSON 对象中某个键的值等于指定内容(如 status = "archived"):
engine.Where("JSON_UNQUOTE(JSON_EXTRACT(extra_data, '$.status')) = ?", "archived").Find(&users)
【不能直接用 engine.Where("extra_data.status = ?", "archived")】 —— 这是无效 SQL,XORM 不会解析 JSON 内部路径。
模糊匹配 JSON 字符串字段(当 JSON 列实际存为 TEXT):
engine.Where("extra_data LIKE ?", `%\"role\":%\"admin\"%`).Find(&users)
该方式依赖字符串匹配,性能差且易误判,仅作兜底,优先升级为原生 JSON 类型并用函数查询。


















