
Go 的 encoding/json 包仅能序列化导出(exported)字段,即首字母大写的字段;小写开头的结构体字段属于包级私有,JSON 编码器无法访问,因此输出为空对象 {}。
go 中结构体字段必须首字母大写才能被 json 编码器导出
在 Go 语言中,encoding/json 包遵循 Go 的可见性规则:只有导出字段(即首字母大写的字段)才能被外部包(包括标准库)访问和序列化。若结构体字段以小写字母开头(如 name、timeStamp),它们属于未导出字段,json.Encoder 在反射过程中无法读取其值,最终为每个字段生成空值,导致整个结构体被编码为 {}。
例如,原始代码中定义的结构体:
type File struct {
name string // ❌ 未导出,JSON 无法访问
timeStamp int64 // ❌ 未导出,JSON 无法访问
}尽管 fmt.Println(files) 能正常打印(因为 fmt 包可访问同一包内未导出字段),但 json.Encode() 运行时通过反射检查字段导出状态,发现无可用字段,故输出 [{},{}]。
✅ 正确做法是将字段名改为导出形式,并建议同时使用符合 Go 命名惯例的 JSON 标签(可选但推荐):
type File struct {
Name string `json:"name"` // ✅ 导出 + 显式指定 JSON 键名
TimeStamp int64 `json:"timestamp"` // ✅ 导出 + 更自然的键名
}完整修正后的示例程序如下:
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
)
type File struct {
Name string `json:"name"`
TimeStamp int64 `json:"timestamp"`
}
func main() {
var files []File
err := filepath.Walk("/tmp/", func(path string, info os.FileInfo, err error) error {
if err != nil || info == nil || info.IsDir() {
return err
}
name := info.Name()
if len(name) > 3 { // 简单过滤,如需更严谨可用 filepath.Ext()
files = append(files, File{
Name: name,
TimeStamp: info.ModTime().UnixMilli(), // Go 1.17+ 推荐用 UnixMilli() 替代手动换算
})
}
return nil
})
if err != nil {
fmt.Fprintf(os.Stderr, "walk error: %v\n", err)
return
}
fmt.Printf("Go struct: %+v\n", files)
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ") // 可选:美化输出
if err := encoder.Encode(files); err != nil {
fmt.Fprintf(os.Stderr, "JSON encode error: %v\n", err)
}
}? 关键注意事项:
- 不要对切片地址调用 Encode(&files) —— json.Encoder.Encode() 期望接收待编码值本身(如 files),传入 &files 会编码指向切片的指针,虽不报错但语义冗余;直接传 files 即可。
- filepath.Walk 的回调中应检查 info.IsDir() 避免将目录误加入结果。
- Go 1.17+ 引入 time.Time.UnixMilli(),比手动 UnixNano()/1e6 更安全简洁。
- 若需兼容旧版本 Go 或自定义序列化逻辑,可为 File 实现 json.Marshaler 接口,但对简单场景,导出字段 + JSON tag 已足够。
总结:JSON 序列化的前提不是“变量可见”,而是“字段导出”——牢记首字母大写是 Go 结构体与外部世界(JSON、XML、数据库 ORM 等)交互的契约基础。


















