Golang中JSON转换时的类型问题解析

Golang中JSON转换时的类型问题解析 大家好,

我想知道为什么在进行 Marshal 和 Unmarshal 操作时,数字总是被当作 float64 类型处理。

以下是我重现该错误的代码:

package main

import (
	"fmt"
	"encoding/json"
)

type abc struct {
	Name string
	Data interface{}
}
			

func main() {
	var idUser int64 = 1
	a := abc {
		"Abc",
		map[string]interface{}{
			"id_user": idUser, // string - int64
			"name": "a", // string - string
			"id_a": 1, // string - int
			"id_b": 1.2, // string - float
		},
	}
	b, _ := json.Marshal(a)
	
	// verify types
	var data abc
	_ = json.Unmarshal(b, &data)	
	values := data.Data.(map[string]interface{})	
	for k, v := range values{
		fmt.Printf("type key: %T, type value: %T\n", k, v)
	}
}

代码运行链接

这会是 Go 语言 json 包中函数的一个 bug 吗?

在此先感谢大家。


更多关于Golang中JSON转换时的类型问题解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

你好 @NobbZ

感谢回复。

更多关于Golang中JSON转换时的类型问题解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好 @thenorthnate

这涉及到一个外部库。我所做的是复现了这个场景。

感谢你的回复。

由于JSON只识别浮点数,这是正确的。但是,如果你使用类型化的JSON解码器,你也可以将它们读取为 intjson.Number

这或许不言而喻,但如果你提前知道字段的类型,你总是可以在结构体本身中提供类型,而不是使用 interface{}

类似这样:

package main

import (
	"fmt"
)

type Person struct {
	Name string
	Age  int
}

func main() {
	p := Person{
		Name: "John",
		Age:  30,
	}
	fmt.Println(p)
}

在 Go 的 encoding/json 包中,数字被解析为 float64 类型是设计行为,而不是 bug。这是因为 JSON 规范不区分整数和浮点数,而 Go 的 interface{} 类型在解析时需要确定一个具体的数字类型。

当使用 interface{} 接收 JSON 数字时,json.Unmarshal 会默认选择 float64 类型,因为它可以无损地容纳 JSON 规范中定义的所有数字值(包括整数和浮点数)。这是为了确保不会丢失精度。

以下是验证和解决这个问题的示例:

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	// 原始数据
	data := `{"int": 42, "float": 3.14, "large": 12345678901234567890}`
	
	var result map[string]interface{}
	json.Unmarshal([]byte(data), &result)
	
	// 默认解析为 float64
	for k, v := range result {
		fmt.Printf("%s: %v (type: %T)\n", k, v, v)
	}
	
	// 解决方案1:使用 json.Number
	var result2 map[string]json.Number
	json.Unmarshal([]byte(data), &result2)
	
	fmt.Println("\n使用 json.Number:")
	for k, v := range result2 {
		fmt.Printf("%s: %v (type: %T)\n", k, v, v)
		// 可以转换为具体类型
		if i, err := v.Int64(); err == nil {
			fmt.Printf("  -> int64: %d\n", i)
		}
		if f, err := v.Float64(); err == nil {
			fmt.Printf("  -> float64: %f\n", f)
		}
	}
	
	// 解决方案2:使用具体类型定义结构体
	type MyStruct struct {
		Int   int64   `json:"int"`
		Float float64 `json:"float"`
		Large int64   `json:"large"`
	}
	
	var result3 MyStruct
	json.Unmarshal([]byte(data), &result3)
	
	fmt.Println("\n使用具体类型结构体:")
	fmt.Printf("Int: %d (type: %T)\n", result3.Int, result3.Int)
	fmt.Printf("Float: %f (type: %T)\n", result3.Float, result3.Float)
	fmt.Printf("Large: %d (type: %T)\n", result3.Large, result3.Large)
}

输出结果:

int: 42 (type: float64)
float: 3.14 (type: float64)
large: 1.2345678901234567e+19 (type: float64)

使用 json.Number:
int: 42 (type: json.Number)
  -> int64: 42
  -> float64: 42.000000
float: 3.14 (type: json.Number)
  -> int64: strconv.ParseInt: parsing "3.14": invalid syntax
  -> float64: 3.140000
large: 12345678901234567890 (type: json.Number)
  -> int64: 12345678901234567890
  -> float64: 12345678901234568000.000000

使用具体类型结构体:
Int: 42 (type: int64)
Float: 3.140000 (type: float64)
Large: 12345678901234567890 (type: int64)

对于你的具体案例,可以使用以下解决方案:

package main

import (
	"encoding/json"
	"fmt"
)

type abc struct {
	Name string
	Data map[string]json.Number
}

func main() {
	var idUser int64 = 1
	a := abc{
		"Abc",
		map[string]json.Number{
			"id_user": json.Number(fmt.Sprintf("%d", idUser)),
			"id_a":    "1",
			"id_b":    "1.2",
		},
	}
	
	b, _ := json.Marshal(a)
	
	var data abc
	_ = json.Unmarshal(b, &data)
	
	for k, v := range data.Data {
		fmt.Printf("key: %s, value: %v\n", k, v)
		
		// 根据需要转换为具体类型
		if i, err := v.Int64(); err == nil {
			fmt.Printf("  -> int64: %d\n", i)
		}
		if f, err := v.Float64(); err == nil {
			fmt.Printf("  -> float64: %f\n", f)
		}
	}
}

这种设计确保了 JSON 数字可以正确解析而不会丢失精度,同时提供了 json.Number 类型来延迟数字类型的转换决策。

回到顶部