0

0

Go语言encoding/json包:结构体字段可见性与JSON序列化陷阱解析

花韻仙語

花韻仙語

发布时间:2025-10-03 12:32:01

|

712人浏览过

|

来源于php中文网

原创

Go语言encoding/json包:结构体字段可见性与JSON序列化陷阱解析

本文深入探讨Go语言中使用encoding/json包进行结构体JSON序列化时,为何有时会得到空JSON对象{}的问题。核心原因在于Go的可见性规则:json.Marshal仅能序列化结构体中已导出的(首字母大写)字段。即使err返回nil,若无导出字段,结果仍为空。文章将提供详细的代码示例和解决方案,帮助开发者正确处理Go结构体的JSON序列化。

问题现象:json.Marshal返回空JSON对象

go语言的encoding/json包是处理json数据序列化和反序列化的核心工具。然而,开发者在使用json.marshal将go结构体转换为json字符串时,有时会遇到一个令人困惑的现象:函数返回的json字符串是一个空的{},但同时err变量却是nil,这表明序列化过程“没有错误”。更让人不解的是,原始的go结构体变量本身包含了完整的数据。

以下是一个典型的示例代码,展示了这种问题:

package main

import (
  "encoding/json"
  "fmt"
)

type Address struct {
  street string // 未导出字段
  extended string // 未导出字段
  city string    // 未导出字段
  state string   // 未导出字段
  zip string     // 未导出字段
}

type Name struct {
  first string  // 未导出字段
  middle string // 未导出字段
  last string   // 未导出字段
}

type Person struct {
  name Name      // 嵌套结构体,字段未导出
  age int        // 未导出字段
  address Address // 嵌套结构体,字段未导出
  phone string   // 未导出字段
}

func main() {
  myname := Name{"Alfred", "H", "Eigenface"}
  myaddr := Address{"42 Place Rd", "Unit 2i", "Placeton", "ST", "00921"}
  me := Person{myname, 24, myaddr, "000 555-0001"}

  b, err := json.Marshal(me)

  if err != nil {
    fmt.Println("序列化错误:", err)
  }

  fmt.Println("序列化结果:", string(b)) // 输出: 序列化结果: {}
  fmt.Println("原始结构体:", me)       // 输出: 原始结构体: { {Alfred H Eigenface} 24 {42 Place Rd Unit 2i Placeton ST 00921} 000 555-0001}
}

从输出可以看到,string(b)打印出{},而me结构体却包含了所有数据。这表明问题并非出在数据缺失,而是序列化过程未能正确识别并处理这些数据。

根本原因:Go语言的字段可见性规则

Go语言中,结构体字段的可见性(或称为导出性)由其名称的首字母大小写决定。

  • 首字母大写的字段是已导出(Exported)的,它们可以在包外部被访问,并且可以被encoding/json等标准库进行序列化。
  • 首字母小写的字段是未导出(Unexported)的,它们只能在定义它们的包内部被访问。对于encoding/json包来说,这些未导出的字段是不可见的,因此它们不会被序列化到JSON输出中。

在上述示例代码中,Address、Name和Person结构体中的所有字段(如street, first, age等)都是以小写字母开头的,这意味着它们都是未导出字段。当json.Marshal尝试序列化Person结构体时,它发现没有任何可导出的字段,因此生成了一个空的JSON对象{}。

立即学习go语言免费学习笔记(深入)”;

稿定AI绘图
稿定AI绘图

稿定推出的AI绘画工具

下载

至于err为何为nil,这是因为json.Marshal函数在序列化过程中并没有遇到任何导致失败的错误(例如循环引用、不可序列化的类型等)。它只是成功地序列化了所有它“能看到”的字段——在本例中,即零个可导出字段。因此,从其自身操作的角度来看,序列化是成功的,没有产生错误。

解决方案:导出结构体字段

解决这个问题的关键在于遵循Go语言的可见性规则,将需要序列化到JSON的结构体字段的首字母改为大写,使其成为可导出字段。

package main

import (
  "encoding/json"
  "fmt"
)

// Address 结构体,字段已导出
type Address struct {
  Street   string `json:"street"`    // 使用json tag自定义JSON字段名
  Extended string `json:"extended"`
  City     string `json:"city"`
  State    string `json:"state"`
  Zip      string `json:"zip"`
}

// Name 结构体,字段已导出
type Name struct {
  First  string `json:"first"`
  Middle string `json:"middle,omitempty"` // omitempty 标签表示如果字段为空值则不序列化
  Last   string `json:"last"`
}

// Person 结构体,字段已导出
type Person struct {
  Name    Name    `json:"name"`
  Age     int     `json:"age"`
  Address Address `json:"address"`
  Phone   string  `json:"phone"`
}

func main() {
  myname := Name{"Alfred", "H", "Eigenface"}
  myaddr := Address{"42 Place Rd", "Unit 2i", "Placeton", "ST", "00921"}
  me := Person{myname, 24, myaddr, "000 555-0001"}

  b, err := json.Marshal(me)

  if err != nil {
    fmt.Println("序列化错误:", err)
    return
  }

  fmt.Println("序列化结果:", string(b))
  // 预期输出: {"name":{"first":"Alfred","middle":"H","last":"Eigenface"},"age":24,"address":{"street":"42 Place Rd","extended":"Unit 2i","city":"Placeton","state":"ST","zip":"00921"},"phone":"000 555-0001"}
}

通过将street改为Street,first改为First等,这些字段现在都已导出,json.Marshal函数将能够正确地访问并序列化它们。在上面的示例中,我还额外添加了JSON标签(json:"..."),这是一种常见的做法,用于自定义JSON输出中的字段名称,使其符合常见的camelCase或snake_case约定,同时保持Go结构体字段的首字母大写以满足导出要求。例如,json:"middle,omitempty"表示在JSON中该字段名为middle,且当其为空值时将被省略。

注意事项

  1. 可见性是核心: 始终牢记Go语言的可见性规则是JSON序列化成功的基础。只有已导出的字段才会被encoding/json包处理。
  2. json标签的灵活运用:
    • json:"fieldName":自定义JSON输出中的字段名。这允许Go结构体字段保持大写以导出,而在JSON中则可以采用小写驼峰(camelCase)或下划线(snake_case)等常见命名约定。
    • json:"-":忽略此字段,不进行序列化或反序列化。
    • json:"fieldName,omitempty":如果字段为空值(例如,字符串为空、整数为0、切片为nil等),则在JSON输出中省略此字段。
  3. err返回nil的含义: 在此特定场景下,err为nil并不意味着得到了“预期”的JSON输出,它仅仅表示序列化过程本身没有发生运行时错误。对于空JSON对象的情况,需要检查结构体字段的可见性。
  4. 嵌套结构体: 嵌套结构体中的字段也必须是可导出的,json.Marshal才能正确地深入序列化。
  5. 官方文档: 遇到问题时,查阅encoding/json包的官方文档(go.dev/pkg/encoding/json)是获取最准确和最新信息的重要途径。

总结

Go语言中json.Marshal返回空JSON对象{}的问题,几乎总是由于结构体字段未导出(即首字母小写)造成的。理解并正确应用Go的可见性规则,确保所有需要序列化的字段都以大写字母开头,是解决此问题的根本方法。同时,合理利用json标签可以更精细地控制JSON输出格式。掌握这些基本原则,将有助于开发者高效、准确地处理Go语言中的JSON数据序列化任务。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

408

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

532

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

309

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

74

2025.09.10

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

315

2023.08.02

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

253

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

206

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1463

2023.10.24

c++主流开发框架汇总
c++主流开发框架汇总

本专题整合了c++开发框架推荐,阅读专题下面的文章了解更多详细内容。

80

2026.01.09

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 8.2万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 3.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号