
本文介绍一种自定义 json.unmarshaler 实现——jsonstring 类型,可统一将 json 中的数字、字符串、布尔值、null 等任意原始类型(非对象/数组)安全解码为原始 json 字符串表示,避免序列化丢失精度或格式。
本文介绍一种自定义 json.unmarshaler 实现——jsonstring 类型,可统一将 json 中的数字、字符串、布尔值、null 等任意原始类型(非对象/数组)安全解码为原始 json 字符串表示,避免序列化丢失精度或格式。
在 Go 的标准库中,encoding/json 提供了 json.Number 类型用于精确保留数字的原始字符串形式(如避免浮点数解析导致的精度丢失),但并未提供类似 json.String 的通用原始值捕获机制。当面对结构不确定的 JSON 字段(例如第三方 API 返回的动态类型字段),我们常需将任意 JSON 原始值(string / number / boolean / null)原样转为字符串,而非强制转换为 Go 基础类型。
为此,可实现一个满足 json.Unmarshaler 接口的自定义类型 JsonString:
package main
import (
"encoding/json"
"fmt"
)
type JsonString string
// jsonString 是 JsonString 的别名,用于递归调用 UnmarshalJSON 时避免无限循环
type jsonString JsonString
func (s *JsonString) UnmarshalJSON(data []byte) error {
// 尝试按字符串解码
var str jsonString
if err := json.Unmarshal(data, &str); err == nil {
*s = JsonString(str)
return nil
}
// 尝试按 uint64(覆盖整数)、float64(覆盖浮点数)、bool、null 解码
// 注意:此处直接使用原始字节,不经过 Go 类型解析,确保格式完全保留
var u uint64
if err := json.Unmarshal(data, &u); err == nil {
*s = JsonString(string(data))
return nil
}
var f float64
if err := json.Unmarshal(data, &f); err == nil {
*s = JsonString(string(data))
return nil
}
var b bool
if err := json.Unmarshal(data, &b); err == nil {
*s = JsonString(string(data))
return nil
}
// 处理 null:json.Unmarshal([]byte("null"), &x) 对任何非指针类型返回 nil,因此需显式判断
if string(data) == "null" {
*s = ""
return nil
}
// 其他情况(如空字符串 "")已由第一个 json.Unmarshal(&str) 覆盖
// 若全部失败,返回原始错误(如语法错误)
return fmt.Errorf("cannot unmarshal %s into JsonString", string(data))
}
使用示例:
详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。
func main() {
cases := []string{
`{"number": 123}`,
`{"string": "123"}`,
`{"float": 123.45}`,
`{"bool": true}`,
`{"empty": ""}`,
`{"null": null}`,
}
for _, raw := range cases {
var v struct {
Value JsonString `json:"number,string"`
}
// 注意:字段标签中加 ",string" 可选(仅对数字字段启用字符串模式),但本实现无需依赖它
json.Unmarshal([]byte(raw), &v)
fmt.Printf("%s → %q\n", raw, string(v.Value))
}
}
// 输出:
// {"number": 123} → "123"
// {"string": "123"} → "123"
// {"float": 123.45} → "123.45"
// {"bool": true} → "true"
// {"empty": ""} → ""
// {"null": null} → ""
✅ 关键设计说明:
- 零损耗保留:所有原始 JSON 字面量(如 123、123.0、0.0001)均以字节形式直接截取,不经过 Go 数值类型解析,彻底规避浮点精度丢失与整数溢出风险;
- 语义清晰:null 映射为空字符串 "",符合常见业务逻辑(如字段缺失或空值统一视为空);
- 健壮性:按优先级顺序尝试多种解码路径,并对 null 单独判断,覆盖全部 JSON 原始类型;
- 兼容标准用法:可无缝集成于结构体字段中,无需修改 JSON 标签(即使省略 ,string 亦可工作)。
⚠️ 注意事项:
- 该类型仅适用于 JSON 原始值(scalar),不支持对象 {} 或数组 [] —— 若输入为复合类型,解码将失败;
- 若需支持嵌套结构或更复杂场景,建议结合 json.RawMessage 或 interface{} + 运行时类型判断;
- 生产环境建议增加测试覆盖边界情况(如 -0、Infinity、NaN 等非标准 JSON 值,它们在严格 JSON 中非法,但某些服务可能返回)。
通过 JsonString,你获得了媲美 json.Number 的原始字节控制力,同时扩展至全类型原始值,是构建灵活 JSON 处理管道的重要工具。










