首页 > 后端开发 > Golang > 正文

Go语言中实现JSON字段的单向序列化与反序列化:结构体分离策略

DDD
发布: 2025-10-30 14:50:00
原创
154人浏览过

Go语言中实现JSON字段的单向序列化与反序列化:结构体分离策略

本文探讨了在go语言中如何实现json字段的单向处理,即允许字段从json反序列化(读取)但阻止其在序列化(写入)时出现。针对 `json:"-"` 标签无法满足此需求的问题,文章提出了一种有效的结构体分离策略。通过定义两个语义不同的结构体——一个用于内部完整数据表示,另一个用于外部公共数据传输——可以灵活控制字段的序列化行为,从而实现敏感信息的保护和api接口的精简。

在Go语言的Web服务开发中,我们经常需要处理结构体与JSON数据之间的转换。一个常见的需求是,某个结构体字段在从JSON反序列化(读取)时需要被填充,但在序列化(写入)为JSON响应时却需要被忽略,例如用户密码哈希值、内部密钥等敏感信息。直接使用 json:"-" 标签虽然可以阻止字段被序列化和反序列化,但它无法满足“只读不写”的单向需求。本文将详细介绍一种推荐的解决方案:结构体分离策略。

问题场景与 json:"-" 的局限性

考虑以下 User 结构体,其中 PasswordHash 字段存储了用户的密码哈希值。我们希望在接收用户注册或更新请求时能够从JSON中读取 PasswordHash,但在向客户端返回用户信息时,该字段不应出现在JSON响应中。

type User struct {
    UserName     string   `json:"userName"`
    Projects     []string `json:"projects"`
    PasswordHash string   `json:"passwordHash,omitempty"` // 尝试使用omitempty,但仍会序列化
    IsAdmin      bool     `json:"isAdmin"`
}
登录后复制

如果我们将 PasswordHash 字段标记为 json:"-",它将在所有JSON操作中被忽略,无论是 json.Unmarshal 还是 json.Marshal。这与我们的“读取但跳过写入”的需求相悖。

type User struct {
    UserName     string   `json:"userName"`
    Projects     []string `json:"projects"`
    PasswordHash string   `json:"-"` // 导致无法从JSON中读取PasswordHash
    IsAdmin      bool     `json:"isAdmin"`
}
登录后复制

显然,json:"-" 标签无法实现这种单向控制。

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

解决方案:结构体分离策略

解决此问题的最佳实践是,将输入(反序列化)和输出(序列化)视为不同的语义对象。这意味着为内部数据模型和外部API接口定义不同的结构体。

具体来说,我们可以定义两个结构体:

  1. UserInfo: 包含所有可以公开或需要序列化到客户端的字段。
  2. User: 包含所有内部字段,包括敏感字段,并通过嵌入 UserInfo 来继承其公共字段。

下面是修改后的结构体定义:

序列猴子开放平台
序列猴子开放平台

具有长序列、多模态、单模型、大数据等特点的超大规模语言模型

序列猴子开放平台0
查看详情 序列猴子开放平台
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "log"
)

// UserInfo 结构体包含可公开的用户信息,用于API响应
type UserInfo struct {
    UserName string   `json:"userName"` // 必须唯一
    Projects []string `json:"projects"` // 用户有权访问的项目集合
    IsAdmin  bool     `json:"isAdmin"`  // 用户是否为管理员
}

// User 结构体包含完整的用户数据,包括敏感信息,用于内部存储和反序列化
type User struct {
    UserInfo // 嵌入UserInfo,继承其字段

    // PasswordHash 字段不带JSON标签,默认会进行反序列化
    // 但在序列化UserInfo时会被忽略
    PasswordHash string `json:"passwordHash,omitempty"` // 仅用于反序列化,或在内部使用时方便
}
登录后复制

实现细节与代码示例

通过结构体分离,我们可以轻松实现单向的JSON处理。

反序列化(读取)操作

当从JSON内容读取数据时,我们依然将数据反序列化到完整的 User 结构体中。由于 UserInfo 被嵌入到 User 中,并且 PasswordHash 字段没有 json:"-" 标签,json.Unmarshal 会正确地填充所有字段。

func main() {
    // 模拟接收到的JSON内容
    content := []byte(`{
        "userName": "john.doe",
        "projects": ["projectA", "projectB"],
        "passwordHash": "some_super_secret_hash_value",
        "isAdmin": true
    }`)

    var user User
    err := json.Unmarshal(content, &user)
    if err != nil {
        log.Fatalf("反序列化失败: %v", err)
    }

    fmt.Println("--- 反序列化结果 (User 结构体) ---")
    fmt.Printf("UserName: %s\n", user.UserName)
    fmt.Printf("Projects: %v\n", user.Projects)
    fmt.Printf("PasswordHash: %s\n", user.PasswordHash) // PasswordHash 被成功读取
    fmt.Printf("IsAdmin: %t\n", user.IsAdmin)
    fmt.Println()
}
登录后复制

序列化(写入)操作

当需要将数据序列化为JSON响应时,我们只序列化 User 结构体中的 UserInfo 部分。这样,PasswordHash 字段就会被自动忽略,因为它不属于 UserInfo 结构体。

func main() {
    // ... (接上面的反序列化代码) ...

    // 假设我们现在要将这个用户对象发送给客户端
    // 我们只序列化其公共信息部分 (UserInfo)
    userBytes, err := json.MarshalIndent(user.UserInfo, "", "   ") // 注意这里是 user.UserInfo
    if err != nil {
        log.Fatalf("序列化失败: %v", err)
    }

    fmt.Println("--- 序列化结果 (UserInfo 结构体) ---")
    fmt.Println(string(userBytes))
    // 输出将不包含 "passwordHash" 字段
}
登录后复制

完整的示例代码如下:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "log"
)

// UserInfo 结构体包含可公开的用户信息,用于API响应
type UserInfo struct {
    UserName string   `json:"userName"` // 必须唯一
    Projects []string `json:"projects"` // 用户有权访问的项目集合
    IsAdmin  bool     `json:"isAdmin"`  // 用户是否为管理员
}

// User 结构体包含完整的用户数据,包括敏感信息,用于内部存储和反序列化
type User struct {
    UserInfo // 嵌入UserInfo,继承其字段

    // PasswordHash 字段不带JSON标签,默认会进行反序列化
    // 但在序列化UserInfo时会被忽略
    PasswordHash string `json:"passwordHash"` // 确保反序列化时能正确匹配
}

func main() {
    // 模拟接收到的JSON内容
    content := []byte(`{
        "userName": "john.doe",
        "projects": ["projectA", "projectB"],
        "passwordHash": "some_super_secret_hash_value",
        "isAdmin": true
    }`)

    var user User
    err := json.Unmarshal(content, &user)
    if err != nil {
        log.Fatalf("反序列化失败: %v", err)
    }

    fmt.Println("--- 反序列化结果 (User 结构体) ---")
    fmt.Printf("UserName: %s\n", user.UserName)
    fmt.Printf("Projects: %v\n", user.Projects)
    fmt.Printf("PasswordHash: %s\n", user.PasswordHash) // PasswordHash 被成功读取
    fmt.Printf("IsAdmin: %t\n", user.IsAdmin)
    fmt.Println()

    // 假设我们现在要将这个用户对象发送给客户端
    // 我们只序列化其公共信息部分 (UserInfo)
    var respBuffer bytes.Buffer
    userBytes, err := json.Marshal(user.UserInfo) // 注意这里是 user.UserInfo
    if err != nil {
        log.Fatalf("序列化失败: %v", err)
    }
    json.Indent(&respBuffer, userBytes, "", "   ")


    fmt.Println("--- 序列化结果 (UserInfo 结构体) ---")
    fmt.Println(respBuffer.String())
    // 输出将不包含 "passwordHash" 字段
}
登录后复制

运行上述代码,你会看到 PasswordHash 在反序列化后成功存储在 user 对象中,但在序列化为JSON响应时,它被正确地省略了。

注意事项与总结

  • 语义清晰:这种方法强制我们区分内部数据模型和外部API模型,这使得代码的意图更加清晰,也符合“关注点分离”的原则。
  • 灵活性:如果将来需要暴露 PasswordHash(例如在某个特殊的内部API中),只需序列化 User 结构体即可,而无需修改 UserInfo。
  • 维护性:当结构体字段增减时,只需要在对应的结构体中修改,对其他部分的影响较小。
  • 替代方案:虽然可以通过实现 json.Marshaler 和 json.Unmarshaler 接口来达到类似目的,但对于这种简单的单向需求,结构体分离通常更简洁、易读。自定义接口更适用于需要复杂逻辑处理(如类型转换、数据验证)的场景。

通过采用结构体分离策略,我们可以在Go语言中优雅地实现JSON字段的单向处理,确保敏感数据在传输过程中的安全性,并使API接口更加精简和符合预期。

以上就是Go语言中实现JSON字段的单向序列化与反序列化:结构体分离策略的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

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