
本文介绍在 Go 中使用 encoding/json 库时,如何让嵌套结构体(如用户资料 Profile)在为空时不参与 JSON 序列化,核心方法是将嵌套字段声明为指针类型并配合 omitempty 标签,同时安全处理 SQL 扫描逻辑以避免 nil 指针 panic。
本文介绍在 go 中使用 `encoding/json` 库时,如何让嵌套结构体(如用户资料 profile)在为空时不参与 json 序列化,核心方法是将嵌套字段声明为指针类型并配合 `omitempty` 标签,同时安全处理 sql 扫描逻辑以避免 nil 指针 panic。
在 Go 的 JSON 序列化中,omitempty 标签不会自动识别普通结构体字段是否“逻辑为空”——它只对零值(如 nil 指针、空字符串、0、false 等)生效。因此,若 User.Profile 是值类型 Profile,即使其所有字段均为零值(如 Profile{}),JSON 编码器仍会输出 "Profile": {},这正是你遇到的问题。
✅ 正确解法:将 Profile 字段改为 指针类型,并确保仅在实际存在有效数据时才分配内存:
type User struct {
Id int64 `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Profile *Profile `json:"profile,omitempty"` // ✅ 关键:指针 + omitempty
}
type Profile struct {
Id int64 `json:"id,omitempty"`
UserId int64 `json:"user_id,omitempty"`
Firstname *string `json:"firstname,omitempty"`
Lastname *string `json:"lastname,omitempty"`
}
⚠️ 注意:直接对 &user.Profile.Firstname 调用 Scan 会导致 panic,因为 user.Profile 初始为 nil,解引用 nil *Profile 会触发 “invalid memory address or nil pointer dereference”。
? 安全扫描方案:先扫描到临时变量,再按需构造指针
func GetUsers(c *gin.Context) {
stmt, err := DB.Query(`
SELECT u.id, u.username, u.email, p.firstname, p.lastname
FROM users u
LEFT JOIN profiles p ON u.id = p.user_id
`)
if err != nil {
c.JSON(500, gin.H{"error": "query failed"})
return
}
defer stmt.Close()
var users []User
for stmt.Next() {
var user User
// 临时变量接收 profile 字段(允许 NULL)
var firstName, lastName sql.NullString
// 只扫描顶层字段 + profile 的可空字段(不碰 user.Profile!)
if err := stmt.Scan(
&user.Id,
&user.Username,
&user.Email,
&firstName,
&lastName,
); err != nil {
c.JSON(500, gin.H{"error": "scan failed", "details": err.Error()})
return
}
// 构造 Profile 仅当至少一个 profile 字段非空
if firstName.Valid || lastName.Valid {
profile := Profile{
Firstname: &firstName.String,
Lastname: &lastName.String,
// 注意:Id 和 UserId 在当前 SQL 中未查询,如需填充请补充 SELECT 字段及 Scan 参数
}
user.Profile = &profile
}
// 否则 user.Profile 保持 nil → JSON 中被 omitempty 自动忽略
users = append(users, user)
}
c.JSON(200, users) // 不需要 &users,gin.JSON 已处理切片
}
? 进阶建议:
- 使用 sql.NullString 等类型显式处理 SQL NULL,比 *string 更安全(避免 nil 解引用和空字符串歧义);
- 若 Profile 需包含 Id/UserId,请在 SQL 中显式 SELECT p.id, p.user_id 并在 Scan 中添加对应变量;
- 对于复杂嵌套或高频场景,可封装 ScanIntoUser() 方法,提升复用性与可读性;
- Gin 默认使用标准 encoding/json,无需额外配置即可支持 omitempty 指针逻辑。
✅ 最终效果:有 Profile 的用户输出完整嵌套对象;无 Profile 的用户 JSON 中完全不出现 "Profile" 字段,彻底解决冗余空对象问题。











