DEBUG.WIKI
丽萨主机;为跨境业务选好 IP;原生 / 住宅 IP · 按套餐选择;查看 IP →
阿里云;从建站到云上业务;云服务器 · 数据库 · 对象存储;查看产品 →
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Go 把 JSON 解码到 interface 后,整数 id 变成 float64

encoding/json 默认把接口中的数字解码为 float64。结构已知时指定 int64 字段,动态结构则使用 UseNumber,并检查数字读取和转换错误。

JSON 中的 id 没有小数点,解码到 map[string]interface{} 后,读取它却得到 float64。调试输出有时还会显示科学计数法,让人误以为编码器把整数改成了另一种值。

本文针对 Go 标准库 encoding/json 的默认接口解码规则,不适用于所有第三方 JSON 库。UseNumber() 从 Go 1.1 起提供;下面沿用 interface{} 写法。

用类型输出确认,不靠数字外观判断

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

func main() {
    var data map[string]interface{}
    if err := json.Unmarshal([]byte(`{"id":12423434}`), &data); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%T\n", data["id"])
}

预期输出是 float64。当目标是没有具体数字类型的接口值时,默认解码会用 float64 承接 JSON 数字,不会根据是否有小数点自动选择 int64。

科学计数法只是数字的表示形式,不能单独证明精度已经丢失;序列化后的具体格式也不应被当作判断 Go 动态类型的依据。确认动态类型后,再选择读取方式。

结构已知时,直接使用明确的字段类型

如果 id 的契约就是 int64 范围内的整数,可以直接解码到结构体:

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

type Record struct {
    ID int64 `json:"id"`
}

func main() {
    var record Record
    if err := json.Unmarshal([]byte(`{"id":12423434}`), &record); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%T %d\n", record.ID, record.ID)
}

预期输出为 int64 12423434。检查解码错误是必要的:输入类型不合适或数值超出目标范围时,不能继续把结果当作完整有效记录。

这一做法以字段结构已知为前提。不要为了一个 id 强行丢弃其他未知字段;结构动态变化时可以保留映射,改用下一种解码方式。

动态结构使用 Decoder.UseNumber

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "strings"
)

func main() {
    decoder := json.NewDecoder(strings.NewReader(`{"id":12423434}`))
    decoder.UseNumber()

    var data map[string]interface{}
    if err := decoder.Decode(&data); err != nil {
        log.Fatal(err)
    }
    number, ok := data["id"].(json.Number)
    if !ok {
        log.Fatal("id must be a JSON number")
    }
    id, err := number.Int64()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%T %d\n", id, id)
}

UseNumber() 必须在 Decode 之前设置。这时接口中的数字保存为 json.Number,用 Int64() 明确请求整数,并处理转换错误。缺失字段、null 或字符串形式的 id 都不会通过示例的类型检查。

json.Number 保存数字文本,但重新 Marshal 时仍编码成 JSON 数字,不等同于普通字符串字段。需要浮点运算时可选择 Float64();这也意味着重新接受浮点表示的边界,不能认为 UseNumber 自动让所有运算变成任意精度计算。

不要对已经默认解码出来的 float64 做 .(int64) 类型断言:断言不是数值转换。若需要改变解码策略,应从原始 JSON 重新解码,避免在中间结果上猜测原始数字格式。

验证时检查整数、非整数、超出 int64 范围的数字和缺失 id,确认错误分支符合契约。切换到 UseNumber 后,还要更新依赖原 float64 类型的读取代码;回退时应一并恢复解码和读取逻辑,不能只改其中一端。