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

2022-05-04 18:07:11 +08:00
 lanlanye

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

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

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

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

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

2355 次点击
所在节点    Go 编程语言
13 条回复
ch2
2022-05-04 18:12:58 +08:00
字段写注解,序列化为 json/yaml 时重新命名
honkew
2022-05-04 18:14:10 +08:00
注解
lanlanye
2022-05-04 18:17:30 +08:00
@ch2
@honkew

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

我的领域模型中属性默认都是非导出的(即小写字母开头),这种情况下没法直接添加 json tag 来做序列化,所以我很纠结,是为了序列化将属性改为 public ,还是应该定义一个新的 struct 用来做序列化。
Aoang
2022-05-04 18:21:20 +08:00
对于结构体的玩法,其实就是 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
2022-05-04 18:23:56 +08:00
lanlanye
2022-05-04 18:24:47 +08:00
@Aoang 意思是不要把结构体严格等同于其他语言中的类,在需要时创建匿名结构体来满足需要就可以了吗?
Aoang
2022-05-04 18:31:48 +08:00
@lanlanye #6 呃呃呃,也不是这个意思。

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

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

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

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

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

谢谢各位回复,最后在翻阅标准库的注释时找到了比较官方的解决方案:实现 Marshaler 和 Unmarshaler 接口即可,也就是说为每个 struct 定义 MarshalJSON 和 UnmarshalJSON 方法,之后即可使用标准库 json 进行自定义的序列化和反序列化,也解决了私有属性的序列化问题。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/850796

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX