
go语言在处理json反序列化时,默认会将数字解析为`float64`类型,这可能导致大整数转换为浮点数表示,进而丢失精度或改变格式。本教程将详细介绍如何通过`json.decoder`的`usenumber()`方法,将json中的数字解析为`json.number`类型,从而有效保留其原始字符串形式,确保大整数在序列化和反序列化过程中保持精确性。
当使用encoding/json包进行JSON反序列化时,如果目标类型是interface{},Go语言默认会将JSON中的数字解析为float64类型。对于较小的整数,这通常不是问题。然而,当处理大整数时,float64的精度限制和其科学计数法表示可能导致原始整数值在重新序列化时发生格式变化,甚至在极端情况下丢失精度。
问题示例:
考虑一个包含整数"id": 12423434的JSON字符串。当通过json.Unmarshal将其解析到interface{}(实际为map[string]interface{})时,id的值会被隐式转换为float64类型。随后,如果再次通过json.Marshal将此map序列化回JSON字符串,float64类型的id可能会被表示为科学计数法,例如1.2423434e+07,从而改变了其原始的整数格式。
以下代码演示了这一默认行为:
立即学习“go语言免费学习笔记(深入)”;
package main
import (
"encoding/json"
"fmt"
"os"
)
func main() {
// 原始JSON字符串,包含一个整数id
var b = []byte(`
{
"id": 12423434,
"Name": "Fernando"
}
`)
// 将JSON反序列化到 interface{}
var f interface{}
json.Unmarshal(b, &f) // 此时 id 字段已被解析为 float64 类型
m := f.(map[string]interface{})
// 打印解析后的 map,可以看到 id 已经变为浮点数形式
fmt.Println("解析后的map:", m)
// 将 map 重新序列化回JSON字符串
result, _ := json.Marshal(m) // 重新序列化时,float64 会以科学计数法表示
fmt.Println("重新序列化后的JSON:")
os.Stdout.Write(result)
fmt.Println() // 添加换行,使输出更清晰
}代码输出:
解析后的map: map[id:1.2423434e+07 Name:Fernando]
重新序列化后的JSON:
{"Name":"Fernando","id":1.2423434e+07}从输出可以看出,整数id在经过一次反序列化和一次序列化后,从原始的整数形式变成了浮点数科学计数法形式,这可能不符合预期。
为了解决上述问题,Go语言的encoding/json包提供了json.Number类型。json.Number本质上是一个字符串,它存储了JSON中数字的原始字符串表示,从而避免了在反序列化过程中将其隐式转换为float64。
当我们需要处理无法预先定义结构体的JSON数据,或者需要确保大整数的精确性时,json.Decoder的UseNumber()方法就显得尤为重要。调用UseNumber()后,json.Decoder会将所有数字解析为json.Number类型,而不是float64。
解决方案示例:
以下代码演示了如何使用json.Decoder.UseNumber()来精确处理JSON中的数字:
package main
import (
"encoding/json"
"fmt"
"log"
"strings"
)
var jsonData = `{
"id": 12423434,
"Name": "Fernando",
"big_id": 9223372036854775807, // 一个非常大的整数,int64 的最大值
"decimal_val": 123.456
}`
func main() {
// 创建一个 json.Decoder
d := json.NewDecoder(strings.NewReader(jsonData))
// 调用 UseNumber() 方法,指示解码器将所有数字解析为 json.Number
d.UseNumber()
var x interface{}
if err := d.Decode(&x); err != nil {
log.Fatalf("解码失败: %v", err)
}
fmt.Printf("解码后的数据类型和值: %#v\n", x)
// 检查并处理解析后的字段
if m, ok := x.(map[string]interface{}); ok {
// 访问 id 字段
if idVal, ok := m["id"].(json.Number); ok {
fmt.Printf("id 字段类型为 json.Number,原始字符串: %s\n", idVal.String())
// 可以将其转换为具体类型,例如 int64
if i64, err := idVal.Int64(); err == nil {
fmt.Printf("id 转换为 int64: %d\n", i64)
} else {
fmt.Printf("id 转换为 int64 失败: %v\n", err)
}
}
// 访问 big_id 字段
if bigIdVal, ok := m["big_id"].(json.Number); ok {
fmt.Printf("big_id 字段类型为 json.Number,原始字符串: %s\n", bigIdVal.String())
if i64, err := bigIdVal.Int64(); err == nil {
fmt.Printf("big_id 转换为 int64: %d\n", i64)
} else {
fmt.Printf("big_id 转换为 int64 失败: %v (可能超出 int64 范围)\n", err)
}
}
// 访问 decimal_val 字段
if decVal, ok := m["decimal_val"].(json.Number); ok {
fmt.Printf("decimal_val 字段类型为 json.Number,原始字符串: %s\n", decVal.String())
if f64, err := decVal.Float64(); err == nil {
fmt.Printf("decimal_val 转换为 float64: %f\n", f64)
} else {
fmt.Printf("decimal_val 转换为 float64 失败: %v\n", err)
}
}
}
// 将解析后的数据重新序列化
result, err := json.Marshal(x)
if err != nil {
log.Fatalf("序列化失败: %v", err)
}
fmt.Printf("重新序列化后的JSON: %s\n", result)
}代码输出:
解码后的数据类型和值: map[string]interface {}{"Name":"Fernando", "big_id":"9223372036854775807", "decimal_val":"123.456", "id":"12423434"}
id 字段类型为 json.Number,原始字符串: 12423434
id 转换为 int64: 12423434
big_id 字段类型为 json.Number,原始字符串: 9223372036854775807
big_id 转换为 int64: 9223372036854775807
decimal_val 字段类型为 json.Number,原始字符串: 123.456
decimal_val 转换为 float64: 123.456000
重新序列化后的JSON: {"Name":"Fernando","big_id":9223372036854775807,"decimal_val":123.456,"id":12423434}从输出可以看出,id、big_id和decimal_val字段在解码后都被保留为json.Number类型(其内部是字符串),并在重新序列化时恢复了原始的数字形式,没有转换为浮点数科学计数法,确保了数据的精确性和格式一致性。
如果JSON结构是已知的,最佳实践是定义一个Go结构体来匹配JSON的字段。这种方法提供了更好的类型安全性和代码可读性。
对于普通的整数,可以直接在结构体中使用int、int64等类型。encoding/json包会自动处理这些类型与JSON数字的转换。
如果数字可能非常大,超出int64范围,或者确实需要保持其原始字符串形式,可以将结构体字段定义为json.Number类型。例如:
type MyData struct {
ID json.Number `json:"id"`
Name string `json:"Name"`
BigID json.Number `json:"big_id"`
}
func main() {
var data MyData
err := json.Unmarshal([]byte(jsonData), &data)
if err != nil {
log.Fatalf("Unmarshal failed: %v", err)
}
fmt.Printf("Unmarshal to struct: %+v\n", data)
fmt.Printf("ID as string: %s, BigID as string: %s\n", data.ID.String(), data.BigID.String())
// 重新序列化
res, _ := json.Marshal(data)
fmt.Printf("Marshal from struct: %s\n", res)
}这样,即使不使用json.Decoder.UseNumber(),ID和BigID字段也会被解析为json.Number,从而保留其原始形式。
Go语言的encoding/json包在处理JSON数字时,默认行为是将它们解析为float64。这对于通用interface{}解析和包含大整数的场景可能导致精度问题或格式不符预期。
通过json.Decoder.UseNumber()方法,我们可以指示解码器将所有JSON数字解析为json.Number类型,从而有效保留其原始字符串形式,解决了大整数转换为浮点数的问题。在已知JSON结构的情况下,直接在结构体字段中使用json.Number类型是更明确和推荐的做法。理解并正确应用json.Number是Go语言中处理JSON大整数和保持数据精度的关键。
以上就是Go语言JSON解析:如何避免长整型数据变为浮点数的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号