Go中实体控制部分字段生成json的方法

在Go语言中,控制实体字段生成JSON的方法非常灵活。以下是详细的各种方法:

1. 使用 json 标签控制字段

基本字段控制

go 复制代码
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID       int    `json:"id"`                    // 序列化为 "id"
    Username string `json:"username"`              // 序列化为 "username"
    Password string `json:"-"`                     // 忽略该字段,不参与序列化
    Email    string `json:"email,omitempty"`       // 如果为空值则忽略
    Age      int    `json:"age,omitempty"`         // 如果为0则忽略
    Secret   string `json:"secret,omitempty"`      // 如果为空字符串则忽略
    Status   string                                // 没有json标签,使用字段名 "Status"
}

func main() {
    user := User{
        ID:       1,
        Username: "john_doe",
        Password: "secret123",
        Email:    "",           // 空值
        Age:      0,            // 零值
        Secret:   "confidential",
        Status:   "active",
    }

    data, _ := json.MarshalIndent(user, "", "  ")
    fmt.Println(string(data))
}

输出:

json 复制代码
{
  "id": 1,
  "username": "john_doe",
  "Status": "active"
}

2. 嵌套结构体的字段控制

嵌套结构体示例

go 复制代码
type Address struct {
    Street  string `json:"street,omitempty"`
    City    string `json:"city,omitempty"`
    Country string `json:"country,omitempty"`
    ZipCode string `json:"zip_code,omitempty"`
}

type Profile struct {
    UserID    int     `json:"user_id"`
    FirstName string  `json:"first_name"`
    LastName  string  `json:"last_name"`
    Phone     string  `json:"phone,omitempty"`
    Address   Address `json:"address,omitempty"`
    Internal  string  `json:"-"` // 内部字段,不暴露
}

func main() {
    profile := Profile{
        UserID:    1,
        FirstName: "John",
        LastName:  "Doe",
        // Phone 为空,将被忽略
        Address: Address{
            Street: "123 Main St",
            City:   "New York",
            // Country 和 ZipCode 为空,将被忽略
        },
        Internal: "sensitive data",
    }

    data, _ := json.MarshalIndent(profile, "", "  ")
    fmt.Println(string(data))
}

输出:

json 复制代码
{
  "user_id": 1,
  "first_name": "John",
  "last_name": "Doe",
  "address": {
    "street": "123 Main St",
    "city": "New York"
  }
}

3. 自定义MarshalJSON方法

完全自定义序列化逻辑

go 复制代码
type SensitiveUser struct {
    ID        int    `json:"id"`
    Username  string `json:"username"`
    Email     string `json:"email"`
    Password  string `json:"-"` // 直接忽略
    SecretKey string // 需要自定义逻辑处理
    CreatedAt time.Time
}

// 自定义JSON序列化方法
func (u SensitiveUser) MarshalJSON() ([]byte, error) {
    // 创建匿名结构体,只包含需要暴露的字段
    type Alias SensitiveUser
    return json.Marshal(&struct {
        Alias
        SecretKey    string `json:"-"` // 确保隐藏
        FormattedDate string `json:"created_date"`
    }{
        Alias:        Alias(u),
        SecretKey:    "", // 明确设置为空
        FormattedDate: u.CreatedAt.Format("2006-01-02"),
    })
}

func main() {
    user := SensitiveUser{
        ID:        1,
        Username:  "alice",
        Email:     "alice@example.com",
        Password:  "secret",
        SecretKey: "very-secret-key",
        CreatedAt: time.Now(),
    }

    data, _ := json.MarshalIndent(user, "", "  ")
    fmt.Println(string(data))
}

输出:

json 复制代码
{
  "id": 1,
  "username": "alice",
  "email": "alice@example.com",
  "created_date": "2024-01-15"
}

4. 使用组合结构体实现不同视图

为不同场景创建不同的视图结构体

go 复制代码
// 完整用户结构体(数据库模型)
type FullUser struct {
    ID           int       `json:"id"`
    Username     string    `json:"username"`
    Email        string    `json:"email"`
    PasswordHash string    `json:"-"`
    Phone        string    `json:"phone"`
    CreatedAt    time.Time `json:"-"`
    UpdatedAt    time.Time `json:"-"`
    IsActive     bool      `json:"-"`
}

// 公共视图(API响应)
type PublicUserView struct {
    ID       int    `json:"id"`
    Username string `json:"username"`
}

// 私有视图(用户自己的信息)
type PrivateUserView struct {
    ID       int    `json:"id"`
    Username string `json:"username"`
    Email    string `json:"email"`
    Phone    string `json:"phone,omitempty"`
}

// 转换方法
func (u *FullUser) ToPublicView() PublicUserView {
    return PublicUserView{
        ID:       u.ID,
        Username: u.Username,
    }
}

func (u *FullUser) ToPrivateView() PrivateUserView {
    return PrivateUserView{
        ID:       u.ID,
        Username: u.Username,
        Email:    u.Email,
        Phone:    u.Phone,
    }
}

func main() {
    fullUser := &FullUser{
        ID:           1,
        Username:     "bob",
        Email:        "bob@example.com",
        PasswordHash: "hashed_password",
        Phone:        "123-456-7890",
        CreatedAt:    time.Now(),
        UpdatedAt:    time.Now(),
        IsActive:     true,
    }

    // 不同场景使用不同视图
    publicView := fullUser.ToPublicView()
    privateView := fullUser.ToPrivateView()

    publicJSON, _ := json.MarshalIndent(publicView, "", "  ")
    privateJSON, _ := json.MarshalIndent(privateView, "", "  ")

    fmt.Println("公共视图:")
    fmt.Println(string(publicJSON))
    
    fmt.Println("\n私有视图:")
    fmt.Println(string(privateJSON))
}

输出:

json 复制代码
公共视图:
{
  "id": 1,
  "username": "bob"
}

私有视图:
{
  "id": 1,
  "username": "bob",
  "email": "bob@example.com",
  "phone": "123-456-7890"
}

5. 动态字段控制

根据条件动态选择字段

go 复制代码
type DynamicUser struct {
    ID       int    `json:"id"`
    Username string `json:"username"`
    Email    string `json:"email,omitempty"`
    Phone    string `json:"phone,omitempty"`
    
    // 控制显示级别的标记
    showSensitive bool `json:"-"`
}

// 设置显示级别
func (u *DynamicUser) SetShowSensitive(show bool) {
    u.showSensitive = show
}

// 自定义序列化
func (u DynamicUser) MarshalJSON() ([]byte, error) {
    if u.showSensitive {
        // 显示所有字段
        type Alias DynamicUser
        return json.Marshal(&struct {
            Alias
        }{
            Alias: Alias(u),
        })
    } else {
        // 只显示基本字段
        return json.Marshal(&struct {
            ID       int    `json:"id"`
            Username string `json:"username"`
        }{
            ID:       u.ID,
            Username: u.Username,
        })
    }
}

func main() {
    user := DynamicUser{
        ID:       1,
        Username: "charlie",
        Email:    "charlie@example.com",
        Phone:    "555-0123",
    }

    // 基本视图
    basicJSON, _ := json.MarshalIndent(user, "", "  ")
    fmt.Println("基本视图:")
    fmt.Println(string(basicJSON))

    // 敏感信息视图
    user.SetShowSensitive(true)
    sensitiveJSON, _ := json.MarshalIndent(user, "", "  ")
    fmt.Println("\n敏感信息视图:")
    fmt.Println(string(sensitiveJSON))
}

6. 使用接口实现灵活序列化

接口方式的多视图控制

go 复制代码
// JSONView 接口
type JSONView interface {
    ToJSON() interface{}
}

type Product struct {
    ID          int     `json:"id"`
    Name        string  `json:"name"`
    Description string  `json:"description"`
    Price       float64 `json:"price"`
    Cost        float64 `json:"-"`        // 成本,不对外暴露
    Stock       int     `json:"stock"`
    IsActive    bool    `json:"-"`
}

// 客户视图
func (p *Product) CustomerView() interface{} {
    return struct {
        ID          int     `json:"id"`
        Name        string  `json:"name"`
        Description string  `json:"description"`
        Price       float64 `json:"price"`
    }{
        ID:          p.ID,
        Name:        p.Name,
        Description: p.Description,
        Price:       p.Price,
    }
}

// 管理员视图
func (p *Product) AdminView() interface{} {
    return struct {
        ID          int     `json:"id"`
        Name        string  `json:"name"`
        Description string  `json:"description"`
        Price       float64 `json:"price"`
        Cost        float64 `json:"cost"`
        Stock       int     `json:"stock"`
        IsActive    bool    `json:"is_active"`
    }{
        ID:          p.ID,
        Name:        p.Name,
        Description: p.Description,
        Price:       p.Price,
        Cost:        p.Cost,
        Stock:       p.Stock,
        IsActive:    p.IsActive,
    }
}

func main() {
    product := &Product{
        ID:          1,
        Name:        "Laptop",
        Description: "High-performance laptop",
        Price:       999.99,
        Cost:        650.00,
        Stock:       50,
        IsActive:    true,
    }

    // 客户看到的JSON
    customerData, _ := json.MarshalIndent(product.CustomerView(), "", "  ")
    fmt.Println("客户视图:")
    fmt.Println(string(customerData))

    // 管理员看到的JSON
    adminData, _ := json.MarshalIndent(product.AdminView(), "", "  ")
    fmt.Println("\n管理员视图:")
    fmt.Println(string(adminData))
}

7. 使用结构体嵌入和匿名结构体

灵活的组合方式

go 复制代码
type BaseEntity struct {
    ID        int       `json:"id"`
    CreatedAt time.Time `json:"-"`
    UpdatedAt time.Time `json:"-"`
}

type Article struct {
    BaseEntity
    Title    string `json:"title"`
    Content  string `json:"content"`
    AuthorID int    `json:"author_id"`
    IsDraft  bool   `json:"-"`
}

// 文章列表视图(简洁)
func (a *Article) ListView() interface{} {
    return struct {
        ID        int    `json:"id"`
        Title     string `json:"title"`
        CreatedAt string `json:"created_at"`
    }{
        ID:        a.ID,
        Title:     a.Title,
        CreatedAt: a.CreatedAt.Format("2006-01-02"),
    }
}

// 文章详情视图(完整)
func (a *Article) DetailView() interface{} {
    return struct {
        ID        int    `json:"id"`
        Title     string `json:"title"`
        Content   string `json:"content"`
        AuthorID  int    `json:"author_id"`
        CreatedAt string `json:"created_at"`
    }{
        ID:        a.ID,
        Title:     a.Title,
        Content:   a.Content,
        AuthorID:  a.AuthorID,
        CreatedAt: a.CreatedAt.Format("2006-01-02 15:04:05"),
    }
}

总结

方法 适用场景 优点 缺点
json:"-" 标签 简单字段忽略 简单直接 静态配置,无法动态调整
omitempty 标签 空值字段忽略 自动处理空值 只能处理零值情况
自定义 MarshalJSON 复杂序列化逻辑 完全控制序列化过程 代码复杂度高
多视图结构体 不同API返回不同字段 清晰分离关注点 需要维护多个结构体
动态字段控制 根据条件显示字段 灵活性高 实现相对复杂

最佳实践建议

  1. 对于简单的字段隐藏,使用 json:"-"
  2. 对于可选字段,使用 omitempty
  3. 对于不同的API场景,使用不同的视图结构体
  4. 只有在需要复杂序列化逻辑时才使用自定义 MarshalJSON
  5. 保持JSON序列化的可预测性和一致性