Golang解析JSON的两个常见坑:空指针与数值类型转换

追风的人
追风的人
发布时间:2026-10-15 | 4人浏览过
原创

在Go语言开发中,encoding/json包的使用频率极高,但其中隐藏的两个特性常导致数据异常。第一是空指针序列化结果并非预期的空值,而是字符"null";第二是数值在映射为通用接口时会强制转为float64类型,引发精度丢失或格式错误。

空指针会被解析成字符串"null"

在处理json.Marshal(序列化)和json.Unmarshal(反序列化)时,对空指针的处理逻辑与直觉往往存在偏差。了解这些底层行为有助于避免数据校验失败或数据库写入错误。

第1步:理解序列化中的空指针表现

目的:明确空指针序列化后的真实字节结果。 操作:定义结构体并赋值为空指针,执行Marshal。

type Person struct {
	Name string
	Age  int
}

func main() {
	var p *Person
	bytes, err := json.Marshal(p)
	checkError(err)
	fmt.Printf("len:%d, result:%s\n", len(bytes), string(bytes))  // len:4, result:null
}

func checkError(err error) {
	if err != nil {
		fmt.Printf("err:%+v\n", err)
	}
}

从运行结果可见,当传入空指针时,Marshal并未返回空字符串或报错,而是输出了四个字节的字符串 "null"。这是因为Go的ptrEncoder编码函数在检测到v.IsNil()时,会直接调用e.WriteString("null")并提前返回。这一机制设计用于支持JSON标准中null值的表达。

第2步:分析反序列化时的状态机判断

目的:探究为何输入"null"不报错,而其他乱码会报错。 入口:json.Unmarshal内部首先调用checkValid进行语法检查。

func checkValid(data []byte, scan *scanner) error {
	scan.reset()
	for _, c := range data {
		scan.bytes++
		if scan.step(scan, c) == scanError {
			return scan.err
		}
	}
	if scan.eof() == scanError {
		return scan.err
	}
	return nil
}

checkValid函数使用一个基于状态机的step扫描器逐个解析字符。以初始状态stateBeginValue为例,它会识别特定的JSON关键字首字母。如果第一个字符是n,它将触发针对null的专用状态流转流程:

具体路径如下:

  • 遇到n,进入stateN,要求下一个字符必须是u;
  • 遇到u,进入stateNu,要求下一个字符必须是l;
  • 遇到l,进入stateNul,再次要求下一个字符必须是l;
  • 确认拼写为完整单词后,进入stateEndValue。

因此,合法的json.Unmarshal([]byte("null"), &p)能成功将变量p设为<nil>。而如果字符串是任意无意义字符如s := "abc",由于不符合true、false、null等关键字的严格状态机路径,扫描器会在遇到非预期字符时抛出 invalid character 'a' looking for beginning of value 错误。

int类型会被解析成float64

在使用map[string]interface{}作为JSON解析的目标容器时,数值类型的处理是一个常见的隐患。Go的JSON解析器为了通用性,将所有数字(无论整数还是浮点数)都统一转换为float64类型。

第1步:观察类型转换现象

目的:验证interface{}接收数字后的实际类型变化。 操作:将一个含整数的结构体序列化为JSON,再反序列化到Map中,使用reflect.TypeOf查看类型。

type Person struct {
	Name string
	Age  int
}

func main() {
	p := &Person{
		Name: "text",
		Age:  18,
	}

	bytes, err := json.Marshal(p)
	checkError(err)

	pMap := make(map[string]interface{})
	err = json.Unmarshal(bytes, &pMap)
	checkError(err)
	for k, v := range pMap {
		fmt.Printf("k:%s,v:%+v, vtype:%v\n", k, v, reflect.TypeOf(v))
	}
}

最终打印结果为:k:Age,v:18, vtype:float64。原本的结构体字段是int,但在反序列化过程中被强制转换成了float64。

第2步:应对大数精度丢失问题

风险:当数字超过一定位数时,float64的科学计数法表示会影响后续业务处理。

如果Age的值为1234567,序列化和反序列化后的结果变为:

k:Age,v:1.234567e+06, vtype:float64

这种科学计数法格式在尝试更新关系型数据库(特别是那些严格要求整型字段的地方)时,可能因为无法隐式转换而导致SQL错误或程序崩溃。此外,在高精度的金融或计量场景中,float64可能存在微弱的精度误差。

第3步:建议解决方案

为了避免上述类型转换带来的不可控问题,建议在解析JSON到Map之前,预先构建好具有明确字段类型的结构体(Struct),而不是依赖泛型的interface{}。或者,如果必须使用Map模式且涉及大整数,可以考虑使用第三方库(如use std::collections::HashMap<String, Value>配合特定宏或自定义解组逻辑)或手动解析数字部分后再进行强转,以确保数值的原始精度不被破坏。

简要总结

Go语言在处理JSON时,空指针始终对应null字符串,且解析过程依赖于严格的JSON关键字状态机;同时,通用接口的数字接收者一律采用float64,开发者在处理大数据量整数时需警惕精度丢失和科学计数法格式化。掌握这些底层规则,能有效规避数据序列化过程中的隐蔽BUG。

以上就是Golang解析JSON遇到的坑及解决方法的详细内容,更多关于Golang JSON解析技巧的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表