0

0

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

DDD

DDD

发布时间:2025-10-30 14:50:00

|

186人浏览过

|

来源于php中文网

原创

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 来继承其公共字段。

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

MagickPen
MagickPen

在线AI英语写作助手,像魔术师一样在几秒钟内写出任何东西。

下载
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接口更加精简和符合预期。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

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

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

419

2023.08.07

json是什么
json是什么

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

535

2023.08.23

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

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

311

2023.10.13

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

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

77

2025.09.10

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

240

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

192

2025.07.04

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1133

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

213

2025.10.17

java入门学习合集
java入门学习合集

本专题整合了java入门学习指南、初学者项目实战、入门到精通等等内容,阅读专题下面的文章了解更多详细学习方法。

1

2026.01.29

热门下载

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

精品课程

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

共101课时 | 8.6万人学习

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

共39课时 | 3.2万人学习

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

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