V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
lanlanye
V2EX  ›  Go 编程语言

Go 语言的 JSON 序列化要单独定义一个 struct 吗?

  •  
  •   lanlanye ·
    laipz8200 · 2022-05-04 18:07:11 +08:00 · 2311 次点击
    这是一个创建于 693 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近业务上遇到一些场景产生的疑问:

    假如说我有一套领域模型,定义在 domain/model ,一个模型就是一个 struct ,上面绑定了用于业务的函数(方法)

    但这些模型持久化的时候在数据库里可能是另一种格式,这就可能需要另一套模型(数据库模型),可能在 db/model

    然后开始写接口,然而 request 和 response 又是另一套格式,那这里是不是又需要单独定义一组 struct 用来序列化我的 domain/model ?** 假如 response 结构和原本的 model 只有很小的区别,也需要定义一个新的 struct 用来做序列化吗? **

    类似这样的代码一般是怎么组织的?

    ch2
        1
    ch2  
       2022-05-04 18:12:58 +08:00
    字段写注解,序列化为 json/yaml 时重新命名
    honkew
        2
    honkew  
       2022-05-04 18:14:10 +08:00
    注解
    lanlanye
        3
    lanlanye  
    OP
       2022-05-04 18:17:30 +08:00
    @ch2
    @honkew

    写完发现有个地方没讲清楚,想补充但是超过可编辑时间了,情况是这样的:

    我的领域模型中属性默认都是非导出的(即小写字母开头),这种情况下没法直接添加 json tag 来做序列化,所以我很纠结,是为了序列化将属性改为 public ,还是应该定义一个新的 struct 用来做序列化。
    Aoang
        4
    Aoang  
       2022-05-04 18:21:20 +08:00   ❤️ 1
    对于结构体的玩法,其实就是 Golang 中的组合与嵌套,和面向对象截然不同。

    假定你已经知道并使用过 `omitempty` 等等内容

    - 比如最常见的,需要在序列化时忽略一个字段
    ```golang
    type User struct {
    Username string
    Password string
    }

    func Marshal() {
    json.Marshal(struct {
    *User
    Password string `json:"-"`
    }{})
    }

    ```

    - 添加额外的字段
    ```golang
    type User struct {
    Username string
    Password string
    }

    func Marshal() {
    json.Marshal(struct {
    *User
    Token bool
    }{})
    }

    ```

    - 字段改名
    ```golang
    type User struct {
    Username string
    Password string
    }

    func Marshal() {
    json.Marshal(struct {
    *User
    Password string `json:"-"`
    PasswordHash string
    }{
    User: &User{
    Username: "admin",
    Password: "123456",
    },
    PasswordHash: "123456",
    })
    }

    ```
    Aoang
        5
    Aoang  
       2022-05-04 18:23:56 +08:00
    lanlanye
        6
    lanlanye  
    OP
       2022-05-04 18:24:47 +08:00
    @Aoang 意思是不要把结构体严格等同于其他语言中的类,在需要时创建匿名结构体来满足需要就可以了吗?
    Aoang
        7
    Aoang  
       2022-05-04 18:31:48 +08:00
    @lanlanye #6 呃呃呃,也不是这个意思。

    Golang 写业务算是很麻烦的... 意思其实是,有很多办法可以解决这个问题,但是并不是说那就是最佳实践。
    至于类的问题,Golang 的结构体真和类差距很大。个人也不喜欢类,或者说不喜欢 Java 中的类。

    代码组织良好的情况下,Golang 的组合能让代码看起来更舒适。也会带来更多的问题,例如,如果你需要写 API 文档,用 swagger 的话,你会发现匿名结构体就是灾难。所以我选择不用 swagger 甚至不写 API
    lessMonologue
        8
    lessMonologue  
       2022-05-04 19:37:08 +08:00
    和 OP 组织代码的方式相同,但是 struct 里的字段都是大写,每个模型都有 DTO 、DO 和 VO ,
    lanlanye
        9
    lanlanye  
    OP
       2022-05-04 20:00:05 +08:00
    @Aoang 感谢,我再尝试一下哪种方案更好用吧。

    @lessMonologue 全大写的话意味着破坏了封装性,不过如 Python 之类的语言同样也淡化了这方面的限制,我不太清楚这样做是否会带来什么问题。
    TinyKube
        10
    TinyKube  
       2022-05-04 20:29:26 +08:00 via iPhone
    我现在用的方案是 request 和 entity 区分开,复杂的 response 自定义 Marshal 方法
    TinyKube
        11
    TinyKube  
       2022-05-04 20:32:49 +08:00 via iPhone
    @TinyKube 不过现在从 gorm 转到 ent ,或许可以考虑自定义 Annotation 和 Template 的方案
    janxin
        12
    janxin  
       2022-05-04 20:42:44 +08:00
    首先这个要看你的实践原则是如何的。如果是 API (Design) First 方式,request/response 是一定需要单的结构体进行,甚至还需要 json schema 验证避免带来的破坏性更新。

    如果没有这个前提,API 字段随时可能面对各种变化,那么这个前提下使用相同结构体自然没有什么问题。但是这随时可能带来潜在的破坏性更新。不过这种方式缺点也很明显,会给其他人明显的前置条件阻碍,可能产生臃肿的 API 字段(无用的结果输出),缺少文档之类的。

    我个人建议团队的实践是根据不同的作用域使用单独的模型,也就是 Java 中常说的 DTO 之类的概念。这样可以有效的把风险隔绝在自己的作用域中,有效防止一次修改到处救火的问题。
    lanlanye
        13
    lanlanye  
    OP
       2022-05-04 20:45:30 +08:00   ❤️ 1
    @Aoang
    @lessMonologue
    @TinyKube

    谢谢各位回复,最后在翻阅标准库的注释时找到了比较官方的解决方案:实现 Marshaler 和 Unmarshaler 接口即可,也就是说为每个 struct 定义 MarshalJSON 和 UnmarshalJSON 方法,之后即可使用标准库 json 进行自定义的序列化和反序列化,也解决了私有属性的序列化问题。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5436 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 43ms · UTC 08:50 · PVG 16:50 · LAX 01:50 · JFK 04:50
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.