news 2026/10/1 11:59:34

Go 语言 encoding/json 标准库深度解析:从 Tag 反射到流式处理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Go 语言 encoding/json 标准库深度解析:从 Tag 反射到流式处理

适用版本:Go 1.16 ~ 1.24(文中标注各版本行为差异)

1. 背景

1.1 为什么 JSON 是 Go 生态的"头号公民"数据格式

在 Go 生态中,JSON 的使用频率远超其他序列化格式,几乎每一个网络服务、配置加载、数据交换场景都离不开它。原因有三:

  • HTTP/REST 生态绑定:Go 标准库 net/http(见第 93 篇)以 JSON 作为天然的数据交换格式,encoding/json 与 net/http 同属标准库,开箱即用、零第三方依赖。
  • Web 框架统一心智:Gin(第 97 篇)的 c.ShouldBindJSON、c.JSON,FastAPI(第 96 篇)的 response_model,底层都是同一套 JSON 编解码语义。
  • 结构体优先的静态类型哲学:Go 是强类型语言,encoding/json 的设计核心是结构体 Tag 驱动的反射映射,与 Python 的 dict 自由字典、C++ 的手写序列化完全不同——它是"编译期类型 + 运行时反射"的折中产物。

1.2 Go JSON 设计的三个关键决策

决策具体表现带来的影响
Tag 声明式映射json:"name,omitempty"字段名与 JSON 键解耦,声明式可读性强
反射驱动Marshal/Unmarshal 通过 reflect 遍历结构体通用性强但性能弱于代码生成方案
接口扩展点Marshaler / Unmarshaler / TextMarshaler自定义类型可完全接管自己的序列化逻辑

1.3 为什么单独写这一篇

本系列已有 reflect 篇(第 119 篇)从反射 API 角度剖析动态类型,GORM 篇(第 102 篇)从 ORM 角度看 Tag 应用,zap 篇(第 109 篇)从日志角度谈强类型字段。但encoding/json 作为 Go 标准库中反射的最大消费者、Tag 约定的标准制定者、以及每个 Go 工程师每天都会触碰的包,其 API 全景、底层映射规则、流式处理模式与高频坑点值得一次系统性的专门梳理。本篇就是这条主线。


2. 核心概念

2.1 JSON 数据类型与 Go 类型映射表

JSON 类型Go 接收类型(Unmarshal 时)Go 输出类型(Marshal 时)
nullnil(指针/接口/map/slice 置零)nil 指针/接口/map/slice
true / falseboolbool
数字float64(默认)/ json.Number / 具体整型浮点型int/float64 等所有数值类型
字符串string / []bytestring
数组[]T / [N]Tslice / array
对象map[string]T / structmap / struct

2.2 结构体 Tag 语法

type Field struct { Name string `json:"name"` // 键名映射 Skip string `json:"-"` // 完全忽略 Opt string `json:"opt,omitempty"` // 零值时省略 Str string `json:"str,string"` // 强制编码为 JSON 字符串(引号包裹) NoEscape string `json:"noescape"` // 默认键名 = 字段名 }

Tag 完整语法:json:"<键名>,<选项1>,<选项2>"

选项作用注意
键名指定 JSON 中的键名空串则使用字段名;- 表示忽略
omitempty零值(false/0/""/nil/空 slice/map/长度为 0 的数组)时省略字段自定义 struct 零值不省略,除非实现 IsZero() 或指针
string强制把该字段编码为 JSON 字符串(如 "42")只对数字、bool、string 有效;解码时同样接受字符串形式的数字

2.3 字段选取规则(Marshal/Unmarshal 共用)

  1. 只处理导出字段(首字母大写)。
  2. 字段名大小写不敏感匹配:Name 可匹配 "name" / "Name" / "NAME",优先精确匹配。
  3. Tag 键名精确匹配优先于字段名模糊匹配。
  4. 匿名嵌入字段(embedded struct)默认平铺展开其字段;除非该嵌入字段自身有 Tag 或实现了 Marshaler。
  5. 同一层级多个字段匹配同一键时,深度优先、Tag 优先;同级字段声明靠后者胜出(后写覆盖先写)。

2.4 零值 vs 缺省:解码到结构体的填充语义

Unmarshal 不会把目标结构体清零,而是增量填充:JSON 中出现的键覆盖对应字段,未出现的键保留目标原有值。这在复用缓冲结构体时是特性(可做局部更新),也常是坑(见 §6.15)。


3. API 说明

3.1 顶层函数(一次性编解码)

函数签名说明
Marshalfunc Marshal(v any) ([]byte, error)编码为紧凑 JSON(无缩进、无换行)
MarshalIndentfunc MarshalIndent(v any, prefix, indent string) ([]byte, error)编码并美化缩进
Unmarshalfunc Unmarshal(data []byte, v any) error解码到 v(必须为指针)
Validfunc Valid(data []byte) bool校验是否为合法 JSON,不做解码
Compactfunc Compact(dst *bytes.Buffer, src []byte) error压缩去空白
Indentfunc Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error美化缩进
HTMLEscapefunc HTMLEscape(dst *bytes.Buffer, src []byte)转义 <、>、&、U+2028、U+2029

3.2 流式 API(大文件/网络流)

类型核心方法适用场景
json.EncoderNewEncoder(w io.Writer) / Encode(v) / SetIndent / SetEscapeHTML逐条输出到 http.ResponseWriter、文件、网络连接
json.DecoderNewDecoder(r io.Reader) / Decode(v) / More() / Token() / UseNumber() / DisallowUnknownFields()逐条从流中读取,处理 NDJSON / 未知字段严格模式

3.3 特殊类型与接口

类型/接口作用
json.Number字符串形式的数字,避免 float64 精度丢失;需配合 Decoder.UseNumber() 或结构体字段声明
json.RawMessage延迟解析:先保留原始 JSON 字节,后续再决定如何处理
json.Marshaler接口 MarshalJSON() ([]byte, error),自定义编码
json.Unmarshaler接口 UnmarshalJSON([]byte) error,自定义解码
encoding.TextMarshaler / TextUnmarshaler若类型未实现 JSON 接口,则退化尝试文本接口(如 time.Time)
json.MarshalerErrorMarshal 过程中的包装错误
json.SyntaxError语法错误,含 Offset 字段
json.UnmarshalTypeError类型不匹配错误,含 Field/Value/Offset 字段
json.InvalidUnmarshalError传给 Unmarshal 的非指针或 nil 参数

4. 详细使用说明

示例 1:基础 Marshal / Unmarshal(结构体 + Tag)

package main import ( "encoding/json" "fmt" "log" ) type Device struct { ID int `json:"id"` Name string `json:"name"` Addrs []string `json:"addrs,omitempty"` Active bool `json:"active"` Secret string `json:"-"` Firmware string `json:"firmware,string"` // 数字以字符串形式传输 } func main() { d := Device{ ID: 101, Name: "CNC-Lathe-01", Active: true, Secret: "do-not-serialize", Firmware: "3.2.1", } b, err := json.Marshal(d) if err != nil { log.Fatal(err) } fmt.Println(string(b)) // {"active":true,"firmware":"3.2.1","id":101,"name":"CNC-Lathe-01"} // 注意:Secret 被忽略,Addrs 为空被 omitempty 省略,字段按字典序输出 var got Device if err := json.Unmarshal(b, &got); err != nil { log.Fatal(err) } fmt.Printf("%+v\n", got) }

要点:Marshal 输出按字典序排列字段;omitempty 对空 slice 生效;- 直接忽略;string 选项把数值包成字符串。

示例 2:map 与 slice 的编解码

func mapDemo() { m := map[string]any{ "points": []int{1, 2, 3}, "meta": map[string]string{"k": "v"}, } b, _ := json.Marshal(m) fmt.Println(string(b)) // {"meta":{"k":"v"},"points":[1,2,3]} var back map[string]any _ = json.Unmarshal(b, &back) // 注意:back 中的数字是 float64,points 是 []any fmt.Printf("%T %v\n", back["points"], back["points"]) // []interface {} }

要点:解码到 map[string]any 时所有数字变 float64、嵌套对象变 map[string]any——这是新手最常踩的精度坑(见 §6.1)。

示例 3:自定义 MarshalJSON / UnmarshalJSON

type SafeString string func (s SafeString) MarshalJSON() ([]byte, error) { // 示例:对内容做 Base64 包装(实际业务常做脱敏/加密) return json.Marshal("base64:" + string(s)) } func (s *SafeString) UnmarshalJSON(b []byte) error { var raw string if err := json.Unmarshal(b, &raw); err != nil { return err } *s = SafeString(raw) // 实际应做解码 return nil } type Cmd struct { Payload SafeString `json:"payload"` } func customDemo() { c := Cmd{Payload: "M03 S1200"} b, _ := json.Marshal(c) fmt.Println(string(b)) // {"payload":"base64:M03 S1200"} var c2 Cmd _ = json.Unmarshal(b, &c2) fmt.Println(c2.Payload) // M03 S1200 }

要点:MarshalJSON 返回值必须仍是合法 JSON(通常内部再调 json.Marshal 包装);UnmarshalJSON 接收的是该字段的原始 JSON 片段。

示例 4:流式 Decoder 逐条读取 NDJSON(工业采集场景)

func streamDemo(r io.Reader) error { dec := json.NewDecoder(r) dec.UseNumber() // 保留数字精度 for dec.More() { var record map[string]any if err := dec.Decode(&record); err != nil { return err } // 处理单条记录... _ = record } return nil } // 配合 http 读取 // resp, _ := http.Get(url) // defer resp.Body.Close() // _ = streamDemo(resp.Body)

要点:dec.More() 判断数组内是否还有元素(顶层数组流);UseNumber() 让数字以 json.Number 呈现,避免 float64 精度损失。

示例 5:Encoder 流式写出 + SetIndent 美化

func writeNDJSON(w io.Writer, items []Device) error { enc := json.NewEncoder(w) enc.SetEscapeHTML(false) // 不转义 < > &,省体积 for _, it := range items { if err := enc.Encode(it); err != nil { return err } } return nil }

要点:Encoder.Encode 自带尾部换行,天然适合 NDJSON/日志流;SetEscapeHTML(false) 可提升体积与性能(默认转义 </>/& 以防 XSS)。

示例 6:RawMessage 延迟解析与分发

type Message struct { Type string `json:"type"` Data json.RawMessage `json:"data"` // 先不解析 } func rawDemo(b []byte) { var m Message _ = json.Unmarshal(b, &m) switch m.Type { case "kafka": var k struct{ Topic string `json:"topic"` } _ = json.Unmarshal(m.Data, &k) fmt.Println("kafka topic:", k.Topic) case "mqtt": var q struct{ Qos int `json:"qos"` } _ = json.Unmarshal(m.Data, &q) fmt.Println("mqtt qos:", q.Qos) } }

要点:RawMessage 是"先整体收下、后按需解析"的利器,适合异构消息体/多态分发。

示例 7:json.Number 保精度

func numberDemo() { data := []byte(`{"val": 12345678901234567890}`) var withNum struct { Val json.Number `json:"val"` } _ = json.Unmarshal(data, &withNum) fmt.Println(withNum.Val.String()) // 12345678901234567890(无精度丢失) var withF float64 _ = json.Unmarshal(data, &withF) // 只能配合 map/interface,struct 字段是 float64 会精度丢失 fmt.Println(withF) // 1.2345678901234567e+19 }

要点:超过 2^53 的整数(如 Kafka offset、设备 ID、时间戳)用 float64 必丢精度;字段声明 json.Number 或调用 UseNumber() 是正解。

示例 8:time.Time 与自定义时间格式

type Event struct { At time.Time `json:"at"` } // time.Time 实现了 MarshalJSON/UnmarshalJSON(RFC3339) // 若需要自定义格式,可包一层: type CustomTime time.Time func (t CustomTime) MarshalJSON() ([]byte, error) { return json.Marshal(time.Time(t).Format("2006-01-02 15:04:05")) } func (t *CustomTime) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return err } tt, err := time.Parse("2006-01-02 15:04:05", s) if err != nil { return err } *t = CustomTime(tt) return nil }

要点:time.Time 默认 RFC3339(2026-09-28T10:00:00Z);工业场景常用自定义格式,用类型别名 + 自定义 JSON 接口即可。

示例 9:Decoder 严格模式(DisallowUnknownFields)

func strictDemo(b []byte) error { dec := json.NewDecoder(bytes.NewReader(b)) dec.DisallowUnknownFields() // JSON 中出现结构体不认识的键 → 报错 var cfg struct { Host string `json:"host"` Port int `json:"port"` } if err := dec.Decode(&cfg); err != nil { return fmt.Errorf("未知配置项: %w", err) } return nil }

要点:配置热加载、API 契约校验场景强烈建议开启,能提前暴露"客户端发错字段名"的问题。

示例 10:省略缺失字段与默认值合并

type Config struct { Host string `json:"host"` Port int `json:"port"` Mode string `json:"mode"` } func mergeConfig(defaultCfg Config, patch []byte) (Config, error) { // 先填默认值,再增量 Unmarshal:未出现的键保持默认 cfg := defaultCfg if err := json.Unmarshal(patch, &cfg); err != nil { return cfg, err } return cfg, nil }

要点:利用"Unmarshal 不清零、只覆盖出现的键"的特性做配置补丁合并,比先反序列化成 map 再合并优雅得多。


5. 性能优化

5.1 性能瓶颈在哪里

encoding/json 的性能开销主要来自反射:

  • Marshal 时通过 reflect 遍历结构体字段、读取 Tag、装箱基本类型;
  • Unmarshal 时按 JSON 键名在结构体字段中做线性查找(1.16 之前是纯线性,1.17+ 有轻微优化但仍非哈希索引);
  • 每次调用分配大量临时对象。

官方 benchmark(Go 1.21 前后):Marshal 约 100200MB/s,Unmarshal 约 60120MB/s——远慢于 protobuf/flatbuffers(见第 29/63 篇),但对绝大多数服务足够。

5.2 实用优化清单

手段做法收益
复用 Encoder/Decoder长生命周期对象持有 json.Encoder,避免重复创建减少分配
SetEscapeHTML(false)不需要 HTML 转义时关闭编码更快更小
结构体字段按热度排序高频 JSON 键对应的字段尽量靠前声明(线性查找收益)Unmarshal 微优化
避免 map[string]any强类型结构体 + 明确的字段类型,避免 float64 装箱与再断言类型安全 + 性能
使用 json.Number 仅对精度敏感字段全开会引入字符串解析开销精度与性能平衡
大对象拆批流式 Encoder/Decoder 逐条处理,避免一次性大内存内存峰值下降
预分配 slicemake([]T, 0, n) 减少扩容减少 realloc
终极手段:代码生成easyjson / ffjson / sonic见第 7 节

5.3 池化复用模式

var bufPool = sync.Pool{New: func() any { return &bytes.Buffer{} }} func encodeToJSON(v any) ([]byte, error) { buf := bufPool.Get().(*bytes.Buffer) buf.Reset() defer bufPool.Put(buf) if err := json.NewEncoder(buf).Encode(v); err != nil { return nil, err } // 去掉 Encode 附加的换行 return bytes.TrimRight(buf.Bytes(), "\n"), nil }

注意:buf.Bytes() 返回的切片在 Put 后会被复用,调用方必须立即拷贝或约定同步消费(典型 use-after-free 陷阱)。


6. 常错点/坑

6.1 数字精度丢失(最高频)

json.Unmarshal 到 map[string]any / any 时,数字一律为 float64,超过 2^53 的整数精度丢失。Kafka offset、雪花 ID、设备序列号、毫秒时间戳全中招。

解法:结构体字段用 int64/uint64/json.Number,或 dec.UseNumber()。

6.2 未导出字段静默忽略

结构体中小写字段(id、name)在 Marshal 时被静默跳过、Unmarshal 时静默不填,不报错。排查半天找不到原因。

解法:字段必须导出;需要 JSON 键名不同就用 Tag。

6.3 Tag 键名拼写错误

json:"namee" 与 json:"name" 写错一个字母,Unmarshal 不报错,字段默默为空。

解法:契约测试 + DisallowUnknownFields + 单测断言关键字段。

6.4 零值被 omitempty 误杀

omitempty 对 false、0、"" 都生效。业务上"显式传了 0 表示关闭"的场景,omitempty 会把它吞掉,导致语义改变。

解法:区分"缺省"与"显式零值"用指针字段(*int),指针非 nil 才序列化。

6.5 自定义 struct 的 omitempty 不生效

type Point struct{ X, Y int } type Shape struct { P Point `json:"p,omitempty"` // Point{} 是零值但不省略! }

omitempty 只认基础零值、nil 指针/接口、空 slice/map/array。struct 零值不触发省略,除非实现 func (p Point) IsZero() bool(Go 1.13+ 支持)。

6.6 MarshalJSON 递归死循环

func (t T) MarshalJSON() ([]byte, error) { return json.Marshal(t) // 无限递归!Marshal(t) 又调 t.MarshalJSON }

解法:用类型别名绕开接口:

type alias T func (t T) MarshalJSON() ([]byte, error) { return json.Marshal(alias(t)) }

6.7 Unmarshal 传非指针

json.Unmarshal(b, m) 其中 m 是 map(非指针)→ 返回 json.InvalidUnmarshalError;传结构体值(非指针)同样报错。忘记 & 是高频失误。

6.8 Decoder.Decode 忽略结尾多余 JSON

dec := json.NewDecoder(r) dec.Decode(&v) // 只读第一条,后面的 {"x":1} {"y":2} 不管

流里有多条 JSON 时 Decode 只消费一条,循环要用 More() 或持续 Decode 直到 io.EOF。

6.9 Encoder.Encode 自带换行导致拼接错乱

Encode 输出末尾有 \n。多个 Encoder 输出拼接或用 Compact 时会多出换行,bytes.TrimSpace 处理。

6.10 MarshalIndent 的 prefix 参数误解

json.MarshalIndent(v, "", " ") 的第二个参数是每行前缀(通常空串),第三个才是缩进。把缩进写进 prefix 会出现奇怪缩进。

6.11 HTMLEscape 默认转义 < > &

Marshal/Encode 默认把 <、>、& 转成 \u003c 等(防 XSS)。如果目标是存储/传输而非浏览器渲染,会白涨体积。Encoder.SetEscapeHTML(false) 可关。

6.12 大 JSON 一次性 Unmarshal OOM

几十 MB 到 GB 级 JSON(如采集日志、离线数据)直接 json.Unmarshal 会瞬间吃满内存。用 Decoder 流式逐条处理。

6.13 嵌套 map[string]any 的类型断言链

m := parsed["data"].(map[string]any)["list"].([]any)[0].(map[string]any)["id"].(float64)

每层都要断言,链式断言一个不对就 panic。解法:能结构体就结构体,不能就用 RawMessage 分层解析。

6.14 结构体字段大小写匹配的隐性规则

Unmarshal 时 "NAME" 也能匹配 Name 字段(大小写不敏感)。这通常无害,但存在歧义字段时(ID 与 Id 同时存在)可能填错字段。

6.15 增量填充导致脏数据

重复 Unmarshal 到同一个结构体时,上次残留的字段值不会被清掉。解法:每次用新结构体,或先清零(v = T{})。

6.16 匿名嵌入字段意外平铺

type Base struct{ ID int `json:"id"` } type Wrap struct { Base // 平铺:JSON 是 {"id":1} 而非 {"base":{"id":1}} Name string `json:"name"` }

想保留嵌套层级需要显式字段名:Base Base \json:"base"``。

6.17 time.Time 解析失败

time.Time 只认 RFC3339。"2026-09-28 10:00:00" 这种格式 Unmarshal 直接报错。自定义格式见示例 8。

6.18 数字字符串混传

JSON 里 "port": "8080"(字符串)但 Go 字段是 int → UnmarshalTypeError。对方接口不规范时用 json.Number + 手动转换,或让对方修契约。

6.19 RawMessage 为 null

json:"data" 的值为 null 时,RawMessage 是 "null" 四个字节,不是 nil。直接 Unmarshal 会得到 nil/零值,注意判空:

if len(m.Data) == 0 || string(m.Data) == "null" { /* 处理缺省 */ }

6.20 并发 Marshal/Unmarshal 安全性

encoding/json 的函数级 API 是并发安全的(无共享全局状态),但:

  • 同一个 Encoder/Decoder不能并发使用(内部有缓冲状态);
  • 共享 sync.Pool 中的 buffer 取用后要立即消费;
  • 结构体 Tag 反射缓存(sync.Map 内部实现)并发安全,无需担心。

6.21 Unmarshal 到接口的 nil 陷阱

var v any json.Unmarshal([]byte("null"), &v) // v 为 nil json.Unmarshal([]byte(`{}`), &v) // v 为 map[string]interface{}

空对象与 null 结果不同,v == nil 判断要小心。

6.22 Marshal 循环引用(map/slice 自引用)

map 或 slice 内部引用自身时(m["self"] = m),Marshal 直接死循环崩溃。结构体指针环同样问题(a.Next = a)。

解法:业务上避免自引用数据结构进 JSON;必要时手动打断(深度限制)。

6.23 错误处理不检查

Marshal/Unmarshal 的错误在大部分"看起来能过"的场景不会出现,但一旦出现(如 channel/func 字段无法序列化、JSON 语法错误),不检查就会静默失败。生产代码必须检查 err。

6.24 func/channel/complex 类型无法序列化

Marshal 遇到 func、chan、complex 类型字段返回错误(json: unsupported type)。结构体里藏回调函数时尤其隐蔽。

6.25 大整数转字符串再转回

json:"firmware,string" 把 int64 包成字符串;但 -0、超大整数、特殊浮点(NaN/Inf)在 string 模式下会报错或行为异常。

6.26 JSON 键重复

JSON 中同一个键出现两次({"a":1,"a":2}),Unmarshal 以最后一个为准。这在多来源拼接的数据里是隐蔽 bug。


7. 与第三方 JSON 库的选型对照

库特点性能适用场景
encoding/json(标准库)零依赖、API 稳定、生态兼容基准绝大多数业务、库的默认选择
jsoniter(json-iterator/go)兼容标准库 API、优化迭代器快 1~2 倍想无痛提速的存量项目
easyjson代码生成(模板化),生成专用编解码快 3~5 倍高吞吐网关、核心热路径,愿意接受生成代码进仓库
ffjson代码生成,动态生成反序列化代码快 2~3 倍同 easyjson 但维护热度较低
sonic(字节跳动)JIT 汇编优化,零拷贝快 3~10 倍极致性能需求,需注意平台兼容(x86-64/arm64)与 Go 版本绑定
gjson只解析不反序列化,路径查询快只读单字段提取(如从大 JSON 取一个值),不做类型映射

选型建议:

  • 追求稳定与生态:标准库足够,结合 §5.2 的优化手段;
  • 热路径吞吐瓶颈:先 profile 确认瓶颈在 JSON,再上 easyjson(生成代码可审查、无运行时魔法);
  • 只读提取:gjson.Get(json, "data.items.0.id") 免反序列化。

8. 总结

  1. Tag 是核心心智:json:"name,omitempty,string" 声明式搞定键名映射、零值省略、字符串化三类需求,是 Go JSON 与其他语言最大的区别。
  2. 精度红线:超过 2^53 的整数必须用 int64/json.Number/UseNumber(),map[string]any 默认 float64 是最大的隐性炸弹。
  3. 流式优先:大 JSON、NDJSON、网络流一律 Decoder/Encoder 流式处理,拒绝一次性 Unmarshal。
  4. 接口扩展:MarshalJSON/UnmarshalJSON/RawMessage 三件套覆盖自定义格式、异构分发、延迟解析三类高级场景。
  5. 性能有上限:标准库反射驱动,热路径要提速就上代码生成(easyjson/sonic),先 profile 再优化。
  6. 26 条坑中最高频的是:数字精度、未导出字段、omitempty 误杀、递归死循环、非指针 Unmarshal——写代码时对照检查一遍。

9. FAQ 速查表

Q1:Unmarshal 到 map[string]any 后数字变 float64,怎么保精度?dec.UseNumber() 后值为 json.Number(字符串形式),转 Int64()/Float64() 可控。

Q2:omitempty 为什么对自定义 struct 不生效?omitempty 只认基础零值/nil/空容器。实现 IsZero() bool 方法(Go 1.13+)即可让 struct 支持。

Q3:JSON 键名大小写不敏感吗?Unmarshal 时大小写不敏感匹配(NAME→Name),Marshal 时严格按 Tag/字段名输出。

Q4:如何忽略某个字段?Tag 写 json:"-"。注意 json:"-" 与 json:"-," 不同,后者键名是 "-"。

Q5:字段是 time.Time,格式不对怎么处理?time.Time 只认 RFC3339。自定义格式用类型别名 + 自定义 MarshalJSON/UnmarshalJSON(见示例 8)。

Q6:如何严格校验 JSON 不含未知字段?dec.DisallowUnknownFields()。

Q7:Marshal 结构体里嵌了 func 字段怎么办?func/chan/complex 无法序列化,会报错。用 json:"-" 忽略或自定义 Marshaler。

Q8:为什么我 Unmarshal 后字段是零值?检查:字段是否导出、Tag 键名是否拼错、JSON 里键是否存在、是否被增量填充覆盖、UnmarshalTypeError 被忽略。

Q9:大 JSON 怎么处理?用 Decoder 流式逐条解析,或 gjson 只读提取需要的字段。

Q10:同一个结构体能并发 Marshal 吗?可以,函数级 API 并发安全;但同一 Encoder/Decoder 实例不能并发使用。

Q11:json.Number 和 string 选项有什么区别?string 选项是"强制字符串化"(输出 "42");json.Number 是"保留数字原文不转 float64"。二者解决的问题不同。

Q12:怎样最快判断一段文本是不是合法 JSON?json.Valid(data),不做解码。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 11:58:55

DeepSeek开源大模型技术解析与工程落地指南

这个标题存在根本性事实错误&#xff0c;无法作为真实项目进行技术或商业层面的深度拆解。DeepSeek&#xff08;深度求索&#xff09;是一家中国人工智能公司&#xff0c;成立于2023年&#xff0c;核心业务聚焦于大模型研发与开源生态建设&#xff0c;其产品包括DeepSeek-VL、D…

作者头像 李华
网站建设 2026/10/1 11:58:44

QClaw停运事件解析:从工具下线看腾讯开发者生态演进

我无法根据当前输入生成符合要求的博文内容。 原因在于&#xff1a;您提供的输入内容中&#xff0c; 项目正文、关键词、摘要描述三项全部为空 &#xff0c;仅有一个标题“QClaw 停运&#xff1a;腾讯不养虾&#xff0c;腾讯要当塘主”及若干未展开的提示性短语&#xff08;…

作者头像 李华
网站建设 2026/10/1 11:58:39

Spring面试不背题:核心原理与高频考点全拆解

Spring面试题这个东西&#xff0c;我在面试别人的时候见过太多“背题式”回答了。候选人能把Bean的生命周期八步背得滚瓜烂熟&#xff0c;但你一问“三级缓存到底是怎么处理循环依赖的”&#xff0c;或者“同一个类里两个方法互相调用&#xff0c;事务为什么失效”&#xff0c;…

作者头像 李华
网站建设 2026/10/1 11:58:08

AI原生应用算力瓶颈诊断与优化实战

1. 项目概述&#xff1a;一个被高估的AI原生应用&#xff0c;暴露了大模型落地最真实的算力断层“还没破百万日活&#xff0c;Meta Muse就频频掉链子”——这句话不是调侃&#xff0c;是实打实的系统告警快照。我盯着后台监控面板上连续三天飘红的5xx错误率曲线时&#xff0c;第…

作者头像 李华
网站建设 2026/10/1 11:57:44

HF到MindSpore模型迁移:transformer_config配置解析与实战

去年我们把一个在 Hugging Face 上已经跑到 SFT 阶段的 LLaMA 规模模型迁到 MindSpore Transformers 上训练&#xff0c;原本以为只是换框架导入语句的事&#xff0c;结果第一关就卡在了一份 transformer_config.json 上。同一个文件&#xff0c;在 HF 生态里是模型结构说明书…

作者头像 李华
网站建设 2026/10/1 11:56:47

企业级智能体落地实战:14个生产级LLM+RAG应用案例

1. Grok Bot 团队不是“开源组织”&#xff0c;而是真实存在的工程实践小组 很多人看到“Grok bot 团队分享”第一反应是&#xff1a;又一个蹭X平台热度的营销号&#xff1f;或者误以为这是埃隆马斯克旗下xAI官方团队的对外输出&#xff1f;这里必须先划清边界——这不是官方行…

作者头像 李华