登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go JSON 数字被双引号包住时怎么映射到整数

来源:17golang原创

时间:2026-09-06 03:00:24 434浏览 收藏

接口返回 {"user_id":"123"},而 Go 结构体写成 UserID int,直接反序列化会报类型不匹配。原因是引号让 123 变成了 JSON 字符串,不再是 JSON 数字。若上游始终返回这种格式,给字段加 json:",string" 是最小改动;若上游有时返回 123、有时返回 "123",则应定义自定义类型,明确哪些输入允许转换。

要点速览
  • JSON 的 123"123" 是两种值类型,普通 int 字段不会替你做字符串转整数。
  • 格式固定为数字字符串时,用 json:",string" 直接映射;它不是“忽略所有非数字字符”的宽松开关。
  • 格式混用时,在 UnmarshalJSON 中集中处理 null、小数、空白和溢出,避免业务代码到处转换。

为什么 "123" 不是 Go 的 JSON number

encoding/json 反序列化时会区分 JSON number、JSON string、布尔值和 null。因此下面两个字段的视觉内容相近,类型却不同:

{"count":123}
{"count":"123"}

目标字段若是普通 int,第一行可以按整数解码,第二行则会返回 json.UnmarshalTypeError。这个错误说明输入值和目标 Go 类型不匹配,不是 JSON 语法错误,也不是把字段名改成大写就能解决。先确认上游是否真的把数字序列化成了字符串,再决定接收策略。

Go encoding/json 区分 JSON 字符串 123、JSON 数字 123 与 int 字段的类型映射边界
图1:Go int 字段接收 JSON 数据时,带引号的 123 与未带引号的 123 属于不同输入类型。

固定返回数字字符串时使用 json:",string"

如果接口契约明确规定 count 始终是包在字符串里的整数,可以在字段标签中声明 string 选项。它的含义是:这个数值字段在 JSON 中以字符串承载,标准库负责解开这一层编码。

package main

import (
	"encoding/json"
	"fmt"
)

type Payload struct {
	// 上游固定返回 "123" 时,string 选项负责解除 JSON 字符串包装。
	Count int `json:"count,string"`
}

func main() {
	data := []byte(`{"count":"123"}`)
	var payload Payload
	if err := json.Unmarshal(data, &payload); err != nil {
		// 解析失败时不要继续使用可能不完整的请求对象。
		panic(err)
	}
	fmt.Println(payload.Count)
}

这种写法适合“格式稳定、字段数量不多”的 DTO。它不会把 "12.5" 自动四舍五入成整数,也不应被用来掩盖上游字段契约漂移。若返回的是普通 JSON 数字 123,却把标签写成 json:",string",也要按接口约定重新检查,而不是继续堆兼容分支。

JSON 输入字段定义结果适用判断
123int整数标准 JSON number
"123"int类型错误缺少字符串数值声明
"123"int `json:",string"`整数固定数字字符串
"12.5"整数方案应拒绝不要隐式截断

混合格式要把兼容策略放进自定义类型

有些历史接口在不同版本中返回过 123"123"。此时只加标签不够,因为标签表达的是一种稳定契约。可以定义 FlexibleInt,在一个地方接收允许的输入,再交给 strconv.ParseInt 做范围检查:

type FlexibleInt int64

func (n *FlexibleInt) UnmarshalJSON(data []byte) error {
	if string(data) == "null" {
		// 是否把 null 视为缺省值,要与接口契约保持一致。
		*n = 0
		return nil
	}

	var text string
	if err := json.Unmarshal(data, &text); err == nil {
		// 字符串路径不做四舍五入,只接受整数文本。
		parsed, err := strconv.ParseInt(strings.TrimSpace(text), 10, 64)
		if err != nil {
			return fmt.Errorf("invalid integer string %q: %w", text, err)
		}
		*n = FlexibleInt(parsed)
		return nil
	}

	var number json.Number
	if err := json.Unmarshal(data, &number); err != nil {
		// 其他类型直接返回错误,避免把布尔值当成数字。
		return fmt.Errorf("integer must be a number or string: %w", err)
	}
	parsed, err := strconv.ParseInt(string(number), 10, 64)
	if err != nil {
		return fmt.Errorf("invalid integer number %q: %w", number, err)
	}
	*n = FlexibleInt(parsed)
	return nil
}

示例的兼容面是刻意收窄的:允许整数数字、整数文本和 null,拒绝小数、布尔值和超出 int64 的值。实际项目还应决定 null 是零值、可选字段还是错误;如果字段必须存在,建议用指针或额外的存在性标记表达,而不要让零值承担两种含义。

Go FlexibleInt 自定义 JSON 整数类型处理数字、数字字符串、null、strconv.ParseInt 与溢出错误
图2:自定义 FlexibleInt 把允许的 JSON 表示、整数范围和错误出口集中在一个字段边界。

上线前用边界样例确认整数没有被悄悄改写

这类字段最容易在“能解析”之后留下更隐蔽的问题。不要只测 "123" 一个成功样例,至少把输入类型和业务政策列出来:

  • 格式:是否允许前后空格,是否拒绝小数、科学计数法和空字符串。
  • 范围:目标是 int 还是 int64,服务在 32 位环境上是否仍然安全。
  • 空值:null、缺字段和 "" 是否分别代表缺省、未提供和非法输入。
  • 输出:读取后再次 json.Marshal 时,要输出普通数字还是继续保留字符串形式。
func TestFlexibleInt(t *testing.T) {
	tests := []struct {
		name string
		input string
		want FlexibleInt
		ok   bool
	}{
		{"number", `123`, 123, true},
		{"string", `"123"`, 123, true},
		{"fraction", `"12.5"`, 0, false},
		{"boolean", `true`, 0, false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var got FlexibleInt
			err := json.Unmarshal([]byte(tt.input), &got)
			// 成功样例比较值,失败样例只确认错误存在。
			if (err == nil) != tt.ok {
				t.Fatalf("error = %v, want success = %v", err, tt.ok)
			}
			if err == nil && got != tt.want {
				t.Fatalf("got %d, want %d", got, tt.want)
			}
		})
	}
}

如果只是单个固定字段,优先选结构体标签;如果格式混用或要复用同一规则,使用自定义类型。关键不是“把字符串硬转成整数”,而是把允许的输入集合、位数范围和空值语义写成可复查的代码边界。

常见问题

加了 json:",string" 后为什么普通数字反而不合适?

这个选项表达的是数值被 JSON 字符串包住的格式。普通数字应使用没有该选项的整数字段,混合格式则交给自定义类型。

能不能用 strconv.Atoi 直接转?

可以处理已经拿到的字符串,但它受平台 int 位数影响。需要固定协议范围时,使用 strconv.ParseInt 并指定 bitSize 更清楚。

数字字符串里的空格应该自动去掉吗?

只有接口契约允许时才使用 strings.TrimSpace。若空格代表上游数据错误,应该拒绝并记录原始字段,而不是静默修正。

遇到 Go JSON 数字被双引号包住,先看清楚输入是 123 还是 "123"。固定字符串格式用 json:",string",历史格式混用则把转换策略收口到自定义类型,并用边界测试守住整数范围。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>