Go jsonunmarshal 怎么处理解码类型
来源:17golang原创
时间:2026-09-13 10:51:00 242浏览 收藏
日常写Go代码调用`json.Unmarshal`解析JSON数据的时候,经常会遇到解码类型不符合预期、类型不匹配报错的情况,我们可以通过调整接收结构体、自定义解码方法、适配动态类型等方式处理这类问题。
Go 里常说的“jsonunmarshal 解码类型”,实际入口是标准库 encoding/json.Unmarshal。处理这类问题的关键不是把所有输入先读成 map[string]any,而是先确定目标字段类型:普通字段交给默认规则,格式特殊的字段实现 UnmarshalJSON([]byte) error。如果自定义方法里直接再次解码原类型,还会递归调用自己。
Unmarshal的目标必须是非 nil 指针,结构体字段用json标签明确映射。- 自定义解码优先于默认字段转换;用别名类型承接普通字段,避免方法递归。
- 排错时分别识别 JSON 语法错误、
UnmarshalTypeError和业务校验错误。
你可以优先用指定结构体字段绑定的方式声明接收类型,特殊场景下再用json.Number、自定义UnmarshalJSON方法或者map[string]interface{}做动态适配,不要硬转类型忽略错误。
先确认 json.Unmarshal 进入了哪个类型
标准库会把 JSON 对象映射到结构体,把数组映射到切片或数组,把 JSON 数字映射到目标数字类型。第二个参数如果不是指针,或者传入 nil,入口就无法把结果写回去。字段名默认按名称匹配,也可以用标签固定外部字段名。
package main
import (
"encoding/json"
"fmt"
)
type Order struct {
ID int `json:"id"`
Price float64 `json:"price"`
Tags []string `json:"tags"`
}
func main() {
raw := []byte(`{"id": 17, "price": 8.5, "tags": ["go", "json"]}`)
var order Order
// 传入 &order,Unmarshal 才能把解码结果写回结构体。
if err := json.Unmarshal(raw, &order); err != nil {
// 入口错误先返回,避免继续使用不完整对象。
panic(err)
}
fmt.Println(order.ID, order.Price, order.Tags)
}
如果把 price 写成 JSON 字符串,而目标仍是 float64,默认解码不会替你猜测转换规则。这时要么修正输入契约,要么把“数字或字符串”定义成一个有明确行为的自定义类型。

用 UnmarshalJSON 处理不稳定的字段格式
自定义类型的典型做法是先保留原始 JSON,再根据首字符或候选类型尝试转换。下面的 FlexibleInt 同时接受 JSON 数字和 JSON 字符串,但最终只保存一个 int64,业务层不需要到处判断输入形态。
type FlexibleInt int64
func (v *FlexibleInt) UnmarshalJSON(data []byte) error {
var number int64
// 先按数字读取,成功时不改变字符串分支的语义。
if err := json.Unmarshal(data, &number); err == nil {
*v = FlexibleInt(number)
return nil
}
var text string
// 再接受带引号的数字,并把转换失败原样返回给调用方。
if err := json.Unmarshal(data, &text); err != nil {
return fmt.Errorf("FlexibleInt expects number or string: %w", err)
}
parsed, err := strconv.ParseInt(text, 10, 64)
if err != nil {
return fmt.Errorf("invalid FlexibleInt %q: %w", text, err)
}
*v = FlexibleInt(parsed)
return nil
}
方法使用指针接收者,因为解码需要修改目标值。对于时间、枚举或带单位的字符串,也可以采用相同入口,但应在方法内部明确空值、范围和格式,而不是吞掉错误。
用别名类型切断自定义解码递归
为结构体实现 UnmarshalJSON 后,直接写 json.Unmarshal(data, v) 会再次找到同一个方法,形成递归。解决办法是声明一个底层结构相同但没有方法集的辅助类型,再把特殊字段单独取出。
type Payload struct {
ID int `json:"id"`
Count FlexibleInt `json:"count"`
Meta json.RawMessage `json:"meta"`
}
func (p *Payload) UnmarshalJSON(data []byte) error {
type payloadAlias Payload
var aux payloadAlias
// 别名没有 Payload 的方法集,解码普通字段不会再次进入本方法。
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
*p = Payload(aux)
return nil
}
这里的 FlexibleInt 仍会按自己的规则解码,Meta 则保留原始片段供后续分支处理。若要拒绝拼写错误的字段,可改用 json.Decoder 并调用 DisallowUnknownFields;这属于接口严格程度的选择,不是所有兼容接口都适合开启。

把“解码类型错误”定位到具体字段
排错时不要只打印 err.Error()。JSON 语法坏了通常是 *json.SyntaxError;值和目标类型不匹配时通常是 *json.UnmarshalTypeError,其中 Field 能帮助你定位嵌套字段。自定义方法返回的解析错误则应保留上下文。
var typeErr *json.UnmarshalTypeError
if errors.As(err, &typeErr) {
// Field 给出结构体路径,便于把错误关联到输入字段。
log.Printf("field=%s want=%s got=%s", typeErr.Field, typeErr.Type, typeErr.Value)
}
var syntaxErr *json.SyntaxError
if errors.As(err, &syntaxErr) {
// Offset 表示语法错误附近的字节位置,不等同于业务字段名。
log.Printf("invalid JSON near byte %d", syntaxErr.Offset)
}
最后检查三件事:目标是否为非 nil 指针;自定义方法是否处理了 null 和错误返回;接口是否真的需要拒绝未知字段。这样“解码类型不对”就能落到输入、目标类型或自定义转换中的一个边界上。
相关问题
为什么 Unmarshal 后结构体字段还是零值?
先看传入的是否是指针、字段是否导出、标签是否写对;未匹配的对象键默认会被忽略。
UnmarshalJSON 为什么会无限递归?
通常是方法内部再次把原类型作为目标。使用不带方法集的别名类型承接默认解码即可。
数字和字符串都要兼容,应该用 map[string]any 吗?
不必。为不稳定字段定义小型自定义类型,转换规则集中在一个方法里,结构体其余字段仍保持静态类型。
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
360 收藏
-
361 收藏
-
Golang · Go问答 | 47分钟前 | JSON · 错误处理 · go · encoding/json · 接口边界 · Go json.Unmarshal UnmarshalJSON JSON自定义解码433 收藏
-
118 收藏
-
204 收藏
-
287 收藏
-
266 收藏
-
Golang · Go问答 | 1小时前 | 连接池 · HTTP客户端 · Go问答 · Transport · 生命周期管理 · Go HTTP客户端 连接池 http.Transport CloseIdleConnections Transport生命周期262 收藏
-
Golang · Go问答 | 2小时前 | 连接池 · HTTP客户端 · Go问答 · 端口排查 · Transport · TIME_WAIT httptrace http.Transport Go transport Go端口增长 HTTP连接复用 CLOSE_WAIT297 收藏
-
Golang · Go问答 | 2小时前 | 性能排查 · HTTP客户端 · Go问答 · 连接复用 · Transport · MaxIdleConnsPerHost Go连接池 Go transport http.Transport连接复用 HTTP keep-alive268 收藏
-
Golang · Go问答 | 2小时前 | Context · HTTP客户端 · Go问答 · Transport · 请求超时 · Go请求超时 Go clienttimeout http.Client Timeout Go客户端超时 Transport阶段超时338 收藏
-
465 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习