--- name: Row方法方案审查与修正 overview: 修改 4 个文件:common/objtoobj.go(新增 JsonToObj + 增强 ObjToMap/ObjToSlice)、common/map.go(增强 JsonToMap)、db/query.go(Row 重写)、cache/cache_db.go(用 JsonToObj 替换 json.Unmarshal)。 todos: - id: add-json-safe content: 在 objtoobj.go 新增 JsonToObj(公共) + fixJsonNumbers(私有),增强 ObjToMap/ObjToSlice 的 4 处 json.Unmarshal status: completed - id: fix-json-to-map content: 增强 map.go 的 JsonToMap,使用 UseNumber + fixJsonNumbers status: completed - id: fix-row-method content: 重写 db/query.go 的 Row() + 新增辅助函数 + 更新 import status: completed - id: fix-cache-db content: cache/cache_db.go 的 3 处 json.Unmarshal 改用 JsonToObj status: completed isProject: false --- # Row() + JSON 反序列化类型保留修正方案 ## 修改文件一览 | 文件 | 改动 | | ------------------ | --------------------------------------------------------------------------------- | | common/objtoobj.go | 新增 `JsonToObj` + `fixJsonNumbers`;增强 `ObjToMap`、`ObjToSlice` 的 4 处 json.Unmarshal | | common/map.go | 增强 `Map.JsonToMap` | | db/query.go | 重写 Row() + 新增辅助函数 + 更新 import | | cache/cache_db.go | 3 处 json.Unmarshal 改用 `JsonToObj` | --- ## 文件 1:common/objtoobj.go ### 新增 JsonToObj 和 fixJsonNumbers 在文件 `StrToMap` 之前(约第 378 行前)新增,需要在 import 中添加 `"bytes"`: ```go // JsonToObj 将 JSON 字符串反序列化为 interface{},保留数字原始类型。 // 标准 json.Unmarshal 会把所有 JSON 数字变成 float64,本函数保留整数为 int64、小数为 float64。 // JsonToObj(`{"id":2,"price":1.20}`) → Map{"id": int64(2), "price": float64(1.2)} // JsonToObj(`[1, 2.5, "a"]`) → Slice{int64(1), float64(2.5), "a"} func JsonToObj(jsonStr string) (interface{}, error) { dec := json.NewDecoder(bytes.NewReader([]byte(jsonStr))) dec.UseNumber() var result interface{} if err := dec.Decode(&result); err != nil { return nil, err } return fixJsonNumbers(result), nil } // fixJsonNumbers 递归将 json.Number 转为 int64 或 float64 func fixJsonNumbers(v interface{}) interface{} { switch val := v.(type) { case json.Number: if n, err := val.Int64(); err == nil { return n } if f, err := val.Float64(); err == nil { return f } return string(val) case map[string]interface{}: for k, item := range val { val[k] = fixJsonNumbers(item) } return val case []interface{}: for i, item := range val { val[i] = fixJsonNumbers(item) } return val default: return val } } ``` ### 增强 ObjToMap(2 处 json.Unmarshal) **位置 1** -- string case(约第 27-31 行): ```go // 修改前: case string: v = Map{} e := json.Unmarshal([]byte(obj.(string)), &v) if e != nil { err = errors.New("没有合适的转换对象!" + e.Error()) v = nil } // 修改后: case string: result, e := JsonToObj(val) if e != nil { err = errors.New("没有合适的转换对象!" + e.Error()) v = nil } else if m, ok := result.(map[string]interface{}); ok { v = m } else { err = errors.New("没有合适的转换对象!") v = nil } ``` **位置 2** -- default case(约第 34-44 行): ```go // 修改前: default: data, err := json.Marshal(obj) if err != nil { ... } v = Map{} e := json.Unmarshal(data, &v) if e != nil { ... } // 修改后: default: data, e2 := json.Marshal(obj) if e2 != nil { err = errors.New("没有合适的转换对象!" + e2.Error()) v = nil } else { result, e3 := JsonToObj(string(data)) if e3 != nil { err = errors.New("没有合适的转换对象!" + e3.Error()) v = nil } else if m, ok := result.(map[string]interface{}); ok { v = m } else { err = errors.New("没有合适的转换对象!") v = nil } } ``` ### 增强 ObjToSlice(2 处 json.Unmarshal) **位置 1** -- string case(约第 82-83 行): ```go // 修改前: case string: v = Slice{} err = json.Unmarshal([]byte(obj.(string)), &v) // 修改后: case string: result, e := JsonToObj(val) if e != nil { err = e } else if s, ok := result.([]interface{}); ok { v = s } else { err = errors.New("没有合适的转换对象!") } ``` **位置 2** -- default case(约第 86-91 行): ```go // 修改前: default: v = Slice{} var data []byte data, err = json.Marshal(obj) err = json.Unmarshal(data, &v) // 修改后: default: data, e2 := json.Marshal(obj) if e2 != nil { err = e2 } else { result, e3 := JsonToObj(string(data)) if e3 != nil { err = e3 } else if s, ok := result.([]interface{}); ok { v = s } else { err = errors.New("没有合适的转换对象!") } } ``` --- ## 文件 2:common/map.go ### 增强 JsonToMap(第 183-189 行) 需要在 import 中添加 `"bytes"`: ```go // 修改前: func (that Map) JsonToMap(jsonStr string, err ...*Error) { e := json.Unmarshal([]byte(jsonStr), &that) if e != nil && len(err) != 0 { err[0].SetError(e) } } // 修改后: func (that Map) JsonToMap(jsonStr string, err ...*Error) { dec := json.NewDecoder(bytes.NewReader([]byte(jsonStr))) dec.UseNumber() e := dec.Decode(&that) if e != nil { if len(err) != 0 { err[0].SetError(e) } return } for k, v := range that { that[k] = fixJsonNumbers(v) } } ``` 这里 `fixJsonNumbers` 定义在 `objtoobj.go` 中,同属 `common` 包,可以直接调用。 影响链路: - `StrToMap(s)` → 调用 `JsonToMap(s)` → **自动修复** - `Map.JsonToMap(s)` → **直接修复** --- ## 文件 3:db/query.go -- Row() 重写 (内容与上一版相同,此处只列关键点) ### import 变更 移除 `"reflect"`,新增 `"math"`、`"strconv"`。 ### 新增辅助函数 `dbTypeUnknown/dbTypeInteger/dbTypeFloat/dbTypeDecimal` 常量 + `classifyDBType` + `getDecimalScale` + `getFloatScale` + `fixFloatValue` + `roundFloat` + `convertBytes`。 覆盖 MySQL/SQLite/PostgreSQL 三种数据库的类型名。 ### Row() 方法替换(第 362-391 行) - `defer resl.Close()` + `resl.Err()` 检查 - `ColumnTypes()` 预分类列类型 - type switch 替代 reflect:`[]byte` → `convertBytes`,`float64` → `fixFloatValue`,`float32` → `fixFloatValue`,其余不变 --- ## 文件 4:cache/cache_db.go -- DB 缓存 3 处 `json.Unmarshal` 改用 `JsonToObj`(来自 common 包,已通过 `. "code.hoteas.com/golang/hotime/common"` 导入): **位置 1** -- `getLegacy()` 约第 359-363 行: ```go // 修改前: var data interface{} err := json.Unmarshal([]byte(valueStr), &data) // 修改后: data, err := JsonToObj(valueStr) ``` **位置 2** -- `get()` 约第 411-416 行: ```go // 修改前: var data interface{} err := json.Unmarshal([]byte(valueStr), &data) // 修改后: data, err := JsonToObj(valueStr) ``` **位置 3** -- `CachesGet()` 约第 594-598 行: ```go // 修改前: var data interface{} err := json.Unmarshal([]byte(valueStr), &data) // 修改后: data, err := JsonToObj(valueStr) ``` 不需要新增 import(`JsonToObj` 在 common 包,cache_db.go 已 import)。 --- ## 修正效果总结 | 数据路径 | 修正前 | 修正后 | | ---------------------- | ------------------------------------- | ------------------------------------------------------ | | DB → Row() → Map | float32/float64 原样存入,[]byte 全转 string | 按列类型精确转换:INT→int64, DECIMAL→roundFloat, FLOAT→fixFloat | | DB 缓存 → json.Unmarshal | 所有数字→float64 | JsonToObj:整数→int64, 小数→float64 | | ObjToMap(string) | json.Unmarshal 数字全→float64 | JsonToObj:保留 int64/float64 | | ObjToSlice(string) | json.Unmarshal 数字全→float64 | JsonToObj:保留 int64/float64 | | StrToMap(string) | JsonToMap → json.Unmarshal | JsonToMap → UseNumber + fixJsonNumbers | | Map.JsonToMap(string) | json.Unmarshal | UseNumber + fixJsonNumbers | | Redis 缓存 | ObjToStr → string → ObjToMap | ObjToMap 已增强,**自动修复** | Redis 缓存路径:`set` 用 `ObjToStr` → `get` 返回 string → 调用方用 `ObjToMap` 转回 Map。由于 `ObjToMap` 的 string 分支现在使用 `JsonToObj`,Redis 缓存也**自动获得类型保留**,无需单独改 cache_redis.go。