当前位置:首页 > 文章列表 > Golang > Go问答 > Go jsonunmarshal 怎么处理解码类型

Go jsonunmarshal 怎么处理解码类型

来源:17golang原创 2026-09-13 10:51:00 0浏览 收藏

日常写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,默认解码不会替你猜测转换规则。这时要么修正输入契约,要么把“数字或字符串”定义成一个有明确行为的自定义类型。

Go encoding/json.Unmarshal 从目标指针分派到自定义解码、默认字段映射和类型错误的静态关系图
图1:Go JSON 解码类型分派示意图,展示入口、方法优先级、默认映射与错误出口的关系。

用 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;这属于接口严格程度的选择,不是所有兼容接口都适合开启。

Go Payload 自定义 UnmarshalJSON 通过 payloadAlias、FlexibleInt 和 RawMessage 分担字段解码的静态结构图
图2:自定义解码与别名类型示意图,展示 Payload、辅助别名、特殊字段和 RawMessage 的静态关系。

把“解码类型错误”定位到具体字段

排错时不要只打印 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 吗?

不必。为不稳定字段定义小型自定义类型,转换规则集中在一个方法里,结构体其余字段仍保持静态类型。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Optional orElse 求值怎么配置或排查Optional orElse 求值怎么配置或排查
上一篇
Optional orElse 求值怎么配置或排查
sqlite3 autocommit怎么配置或排查
下一篇
sqlite3 autocommit怎么配置或排查
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    111次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    31次使用
  • OpenCompass大模型评测体系详解:功能、使用指南与应用场景
    OpenCompass
    OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
    49次使用
  • AGI-Eval大模型评测平台:权威榜单、数据集与人机协同评测方案
    AGI-Eval
    AGI-Eval是由上海交大等高校联合发布的大模型评测社区,提供公正透明的LLM能力榜单、多领域评测集及Data Studio数据服务,助力AI模型性能评估与NLP科研开发。
    30次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    265次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码