如何在使用DTO处理PATCH部分更新时应对键缺失问题
Go中处理PATCH接口部分更新的几种实用方案
在处理PATCH接口的部分更新时,核心问题是区分「客户端没传该字段」和「客户端主动传了null」,避免把未传字段误更新为NULL。不用map检查字段存在性的话,有这几种靠谱的解决方法:
1. 用指针的指针区分状态
把需要区分的字段改成**string类型,这样三种状态就能明确区分:
- 客户端没传字段:字段值为
nil - 客户端传了
null:字段值为(*string)(nil) - 客户端传了具体值:字段值为指向对应字符串的指针
修改后的结构体:
type UpdateUserDTO struct { ID uuid.UUID FirstName *string LastName *string ImageURL **string }
处理逻辑示例:
// 假设从数据库查询出原始用户数据originalUser if dto.ImageURL != nil { // 客户端传了这个字段,处理null或具体值 if *dto.ImageURL == nil { originalUser.ImageURL = nil // 主动设为数据库NULL } else { originalUser.ImageURL = *dto.ImageURL // 更新为传入的具体值 } } // 没传该字段则直接跳过,保留数据库原有值
这种方法无需额外定义类型,适合字段不多的简单场景,但嵌套指针的写法读起来稍显绕。
2. 自定义可选类型跟踪字段是否被设置
定义一个带标记的自定义类型,实现UnmarshalJSON接口,主动标记该字段是否被客户端传入。
先定义可选字符串类型:
type OptionalString struct { Value string Set bool // 标记是否被客户端设置过 } // 只要触发解码就标记Set为true,区分未传字段的默认状态 func (os *OptionalString) UnmarshalJSON(data []byte) error { os.Set = true // 如果传的是null,直接返回,Value保持默认空字符串 if string(data) == "null" { return nil } // 解析具体值到Value return json.Unmarshal(data, &os.Value) }
更新结构体:
type UpdateUserDTO struct { ID uuid.UUID FirstName OptionalString LastName OptionalString ImageURL OptionalString }
处理逻辑清晰直观:
if dto.ImageURL.Set { // 客户端传了这个字段,判断是null还是具体值 if string(data) == "null" { originalUser.ImageURL = nil } else { originalUser.ImageURL = &dto.ImageURL.Value } } // 未设置则跳过,保留数据库原有值
这种方法扩展性强,不管是string、int还是其他类型,都可以套用这个模板定义对应的Optional类型,代码可读性也更高。
3. 自定义SQL Null类型(贴近数据库交互场景)
如果你的数据库字段用的是sql.NullString这类类型,可以修改它的解码逻辑,让「未传字段」和「传null」区分开。默认的sql.NullString无法区分这两种状态,所以需要自定义扩展:
type CustomNullString struct { sql.NullString Set bool // 标记是否被客户端传入 } func (c *CustomNullString) UnmarshalJSON(data []byte) error { c.Set = true if string(data) == "null" { c.Valid = false return nil } if err := json.Unmarshal(data, &c.String); err != nil { return err } c.Valid = true return nil }
结构体里使用CustomNullString,处理时检查Set属性即可,逻辑和第二种方案类似。这种方案更贴近数据库类型,适合直接和数据库交互的场景。
内容的提问来源于stack exchange,提问作者lkp
相关产品推荐
相关产品推荐

