当前位置:首页 > 文章列表 > Golang > Go教程 > Go time.Duration 序列化成 JSON 怎么避免单位误解

Go time.Duration 序列化成 JSON 怎么避免单位误解

来源:17golang原创 2026-11-10 23:15:34 0浏览 收藏

Go 的 time.Duration 本质上是一个以纳秒为单位的 int64。因此,直接把它交给传统的 encoding/json 时,1500 * time.Millisecond 会变成 JSON 数字 1500000000,调用方很容易误读成 1500 毫秒、1500 秒,甚至不知道单位。更稳妥的做法是先选定协议:跨语言配置优先传带单位的字符串;必须传数字时,就把单位写进字段名和接口文档。

如果目标是可读配置,使用 "1.5s" 这样的字符串并在 Go 端调用 time.ParseDuration;如果目标是数字协议,则使用 timeout_ms 或 timeout_ns,不要让裸数字代表一个没有名字的 Duration。

先看清 time.Duration 的默认含义

官方 time 包把 Duration 定义为两个时间点之间的耗时,底层是纳秒计数,最大可表示范围约为 290 年。它的 String 方法则会输出 1h30m0.5s、250ms 这类带单位文本;ParseDuration 能读取 ns、us、µs、ms、s、m、h。

这两个表示不能混为一谈:JSON 数字是底层计数,字符串是面向协议的格式。下面的关系图把字段、数值和单位边界放在一起,阅读代码时先看“协议边界”这一组。

Go time.Duration 从配置字段到纳秒计数和带单位字符串的静态关系说明图
图1:结构说明图,比较 Duration 底层计数、JSON 数字和带单位字符串之间的静态关系。

可读配置使用带单位字符串

对于超时、退避间隔、缓存 TTL 等配置,我更愿意把字符串作为公开契约。它能让配置审查和跨语言对接直接看出单位,解析失败也能在入口处返回错误,而不是把一个错误的裸数字带进业务。

package main

import (
	"encoding/json"
	"fmt"
	"time"
)

// JSONDuration 把 Duration 固定为带单位的 JSON 字符串。
type JSONDuration struct {
	time.Duration
}

// MarshalJSON 保留 1.5s、250ms 这样的可读单位。
func (d JSONDuration) MarshalJSON() ([]byte, error) {
	return json.Marshal(d.Duration.String())
}

// UnmarshalJSON 在配置入口解析单位并拒绝非法文本。
func (d *JSONDuration) UnmarshalJSON(data []byte) error {
	var raw string
	if err := json.Unmarshal(data, &raw); err != nil {
		return fmt.Errorf("duration must be a string: %w", err)
	}
	parsed, err := time.ParseDuration(raw)
	if err != nil {
		return fmt.Errorf("invalid duration %q: %w", raw, err)
	}
	d.Duration = parsed
	return nil
}

type Config struct {
	// RetryAfter 的 JSON 值示例为 "1.5s",不再依赖隐含单位。
	RetryAfter JSONDuration `json:"retry_after"`
}

这样编码后的字段是 "retry_after":"1.5s",解码时也只接受有明确单位的字符串。若协议规定零值合法,0s 会自然保留;若零值表示“未配置”,应在配置层增加指针或显式开关,而不是让调用方猜测。

数字协议要把单位写进名字

有些旧接口只能传数字,或者下游已经固定为整数毫秒。这时可以继续使用整数,但不要直接暴露 time.Duration 的 JSON 默认结果。字段名应把单位写出来,转换只发生在边界处:

type NumericConfig struct {
	// 字段名明确约定:这里的数字永远是毫秒。
	TimeoutMS int64 `json:"timeout_ms"`
}

func (c NumericConfig) Duration() time.Duration {
	// 只在进入 Go 业务层时转换,避免内部到处重复乘单位。
	return time.Duration(c.TimeoutMS) * time.Millisecond
}

func fromDuration(d time.Duration) NumericConfig {
	// 取整策略要写进协议;这里向零截断为毫秒。
	return NumericConfig{TimeoutMS: d.Milliseconds()}
}

timeout_ms: 1500 和 timeout_ns: 1500000000 都可以成立,关键是名称、文档、校验和转换函数必须一致。若一个服务输出纳秒、另一个服务按毫秒读取,数值通常不会报错,却会让超时扩大或缩小一百万倍,这比显式解析错误更难排查。

Go Duration 字符串解析、数字单位转换和业务配置边界的静态关系说明图
图2:边界说明图,查看字符串解析、明确单位整数和业务层 Duration 的关系。

兼容旧数据时先定规则再写代码

如果已有 JSON 中保存的是裸数字,第一步不是直接改成字符串,而是查清历史数字的真实单位。传统 encoding/json 对 time.Duration 的默认数字表示是纳秒;但某个业务 DTO 可能早已手工定义成毫秒。迁移时建议按下面的顺序处理:

  • 给旧字段建立样本,确认生产数据的单位和取值范围。
  • 新增版本化字段,例如同时接受旧的 timeout 和新的 timeout_text,输出只保留一种规范格式。
  • 对负数、超大值、空字符串和小数分别设定规则;不要让溢出或隐式取整静默通过。
  • 在跨语言文档中写出 JSON 示例,并注明 time.ParseDuration 支持的单位集合。

如果使用的是新的 encoding/json/v2,也不要假设旧的默认行为会自动延续。官方文档说明,Duration 需要显式表示方式;纳秒数字需要使用对应选项。无论采用哪一版 JSON 包,本文的核心原则都不变:协议的单位必须显式可见。

最后用一张表锁定协议选择

场景推荐 JSON重点边界
人读配置、跨语言接口"1.5s"入口用 ParseDuration,非法单位直接报错
兼容固定整数接口1500 配合 timeout_ms只在边界转换,明确取整和范围
旧字段迁移兼容旧读、规范新写先确认旧数据单位,再设计版本策略

把 time.Duration 看成“带单位的业务量”,而不是普通整数,JSON 序列化就不会再靠猜。写字符串协议时让值自描述;写数字协议时让字段名自描述,这两条足以挡住大多数单位误解。

常见疑问

为什么不能直接把 Duration 的数字当成毫秒?

因为 Duration 的底层单位是纳秒。只有在字段或转换函数明确约定为毫秒时,数字才有毫秒语义。

字符串一定比数字更好吗?

不一定。字符串更适合可读配置和跨语言协议;高吞吐的固定整数接口可以使用毫秒或微秒,但必须把单位写进字段名、文档和边界转换。

官方资料:https://pkg.go.dev/time#Duration、https://pkg.go.dev/encoding/json

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go time.Duration 的 Round 和 Truncate 遇到负值有什么区别Go time.Duration 的 Round 和 Truncate 遇到负值有什么区别
上一篇
Go time.Duration 的 Round 和 Truncate 遇到负值有什么区别
Go crypto/ecdh 公钥 Bytes 怎么恢复为 PublicKey
下一篇
Go crypto/ecdh 公钥 Bytes 怎么恢复为 PublicKey
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    971次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    980次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1090次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1063次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    903次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码