在Go语言的实际开发场景中,我们经常会遇到JSON数据里的整数被编码为字符串类型,同时该字段还可能携带空值的特殊情况,直接使用标准库的json.Unmarshal方法处理这类数据很容易出现解析错误,需要针对性的设计处理方案。

问题场景说明
假设我们接收到的JSON数据格式如下,其中age字段是整数但被编码为字符串,且可能存在空值:
{
"name": "张三",
"age": "18"
}
// 或者空值场景
{
"name": "李四",
"age": ""
}
如果直接定义结构体用string类型接收age字段,后续转整数还需要额外处理,且空值转整数时会出错;如果用int类型接收,反序列化时字符串类型的"18"会直接报错,无法完成解析。
方案一:自定义类型实现UnmarshalJSON方法
我们可以自定义一个类型,为它实现json.Unmarshaler接口,在反序列化逻辑中处理字符串转整数和空值的场景。
package main
import (
"encoding/json"
"errors"
"fmt"
"strconv"
)
// 定义自定义类型,底层类型为int,用于存储解析后的整数
type StringInt int
// 实现UnmarshalJSON方法,处理字符串编码的整数和空值
func (s *StringInt) UnmarshalJSON(data []byte) error {
// 先去除JSON数据的前后引号
str := string(data)
if str == "null" || str == """" {
// 空值或null时设置为0
*s = 0
return nil
}
// 去除字符串的首尾引号
if len(str) >= 2 && str[0] == '"' && str[len(str)-1] == '"' {
str = str[1 : len(str)-1]
}
// 转换为整数
num, err := strconv.Atoi(str)
if err != nil {
return errors.New("字符串转整数失败: " + err.Error())
}
*s = StringInt(num)
return nil
}
// 定义接收JSON的结构体
type User struct {
Name string `json:"name"`
Age StringInt `json:"age"`
}
func main() {
// 测试正常字符串整数场景
jsonStr1 := `{"name":"张三","age":"18"}`
var user1 User
err := json.Unmarshal([]byte(jsonStr1), &user1)
if err != nil {
fmt.Println("解析失败:", err)
} else {
fmt.Printf("用户1姓名: %s, 年龄: %dn", user1.Name, user1.Age)
}
// 测试空值场景
jsonStr2 := `{"name":"李四","age":""}`
var user2 User
err = json.Unmarshal([]byte(jsonStr2), &user2)
if err != nil {
fmt.Println("解析失败:", err)
} else {
fmt.Printf("用户2姓名: %s, 年龄: %dn", user2.Name, user2.Age)
}
}
方案二:使用json.RawMessage二次处理
如果不想自定义类型,也可以先使用json.RawMessage接收字段原始数据,再手动处理字符串和空值的情况。
package main
import (
"encoding/json"
"fmt"
"strconv"
)
type User struct {
Name string `json:"name"`
Age json.RawMessage `json:"age"`
AgeInt int // 实际存储的整数年龄
}
func (u *User) UnmarshalJSON(data []byte) error {
// 先定义临时结构体接收原始数据
type TempUser struct {
Name string `json:"name"`
Age json.RawMessage `json:"age"`
}
var temp TempUser
if err := json.Unmarshal(data, &temp); err != nil {
return err
}
u.Name = temp.Name
u.Age = temp.Age
// 处理Age字段
ageStr := string(temp.Age)
if ageStr == "null" || ageStr == """" {
u.AgeInt = 0
return nil
}
// 去除引号
if len(ageStr) >= 2 && ageStr[0] == '"' && ageStr[len(ageStr)-1] == '"' {
ageStr = ageStr[1 : len(ageStr)-1]
}
num, err := strconv.Atoi(ageStr)
if err != nil {
return err
}
u.AgeInt = num
return nil
}
func main() {
jsonStr1 := `{"name":"张三","age":"18"}`
var user1 User
err := json.Unmarshal([]byte(jsonStr1), &user1)
if err != nil {
fmt.Println("解析失败:", err)
} else {
fmt.Printf("用户1姓名: %s, 年龄: %dn", user1.Name, user1.AgeInt)
}
jsonStr2 := `{"name":"李四","age":""}`
var user2 User
err = json.Unmarshal([]byte(jsonStr2), &user2)
if err != nil {
fmt.Println("解析失败:", err)
} else {
fmt.Printf("用户2姓名: %s, 年龄: %dn", user2.Name, user2.AgeInt)
}
}
两种方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 自定义类型实现UnmarshalJSON | 复用性高,定义一次后可在多个结构体中使用,代码结构清晰 | 需要额外定义类型,对于简单场景稍显繁琐 |
| json.RawMessage二次处理 | 无需额外定义通用类型,适合单个结构体的特殊场景 | 复用性低,每个需要处理的字段都要写重复的处理逻辑 |
注意事项
- 处理字符串时一定要注意去除JSON编码自带的前后引号,否则转换整数时会失败
- 空值场景要同时考虑空字符串和null两种情况,避免遗漏导致解析错误
- 如果业务中对空值有特殊的默认值要求,可以修改对应逻辑中的默认值设置
GoJSON_Unmarshaling字符串编码整数空值处理修改时间:2026-06-27 04:27:29