Golang中如何序列化SQL类型
Golang中如何序列化SQL类型 我正在开始学习Go语言。为此,我基于Postgres(存储过程/函数)创建了一个小型REST-API,它本身运行良好。
然而,我注意到“标准编组器”生成的JSON输出与我预期的略有不同。
下面你可以看到我在所有服务中使用的模型(结构体):
type Aufgabe struct {
ID int `json:"id"`
Beschreibung string `json:"beschreibung"`
StatusCode int `json:"statusCode"`
StatusText string `json:"statusText"` // nullable, may be not present
KategorieCode int `json:"kategorieCode"`
KategorieText string `json:"kategorieText"` // nullable, may be not present
ErstelltAm time.Time `json:"erstelltAmTS"`
ZuErledigenBis sql.NullTime `json:"zuErledigenBisTS"` // nullable, may be not present, Date
Kosten sql.NullFloat64 `json:"kosten"` // nullable, may be not present
DauerMin sql.NullInt32 `json:"dauerMin"` // nullable, may be not present
Wichtig bool `json:"wichtig"`
ErledigtAm sql.NullTime `json:"erledigtAmTS"` // nullable, may be not present
}
输出结果如下:
{
"id": 5,
"beschreibung": "sdsd",
"statusCode": 2,
"statusText": "gelöscht",
"kategorieCode": 2,
"kategorieText": "einkauf",
"erstelltAmTS": "2020-09-15T18:45:39.974309Z",
"zuErledigenBisTS": {
"Time": "2020-09-22T00:00:00Z",
"Valid": true
},
"kosten": {
"Float64": 0,
"Valid": false
},
"dauerMin": {
"Int32": 0,
"Valid": false
},
"wichtig": false,
"erledigtAmTS": {
"Time": "0001-01-01T00:00:00Z",
"Valid": false
}
}
显然,这些“SQL类型”本身是对象。我希望只返回实际值,并且可能还需要格式化时间戳。
这里有一个使用node.js生成的相同结构的示例:
{
"id": 5,
"beschreibung": "sdsd",
"statusCode": 2,
"statusText": "gelöscht",
"kategorieCode": 2,
"erstelltTS": "2020-09-15T16:45:39.974Z",
"kategorieText": "einkauf",
"kosten": null,
"dauerMin": null,
"wichtig": false,
"erledigtAmTS": null
}
你可以看到它只包含实际值,并为空值打印null。
我在Go版本中遇到的另一个问题是,“添加/创建”服务也期望这些对象类型的JSON结构,如果客户端只提供了值,则无法映射实际的sql类型。
换句话说:在Golang中处理JSON和数据库的最佳实践是什么?我读过关于自定义编组器的内容,但这对我来说太复杂了,难以理解。也许我应该使用另一个库?
更多关于Golang中如何序列化SQL类型的实战教程也可以访问 https://www.itying.com/category-94-b0.html
omsec:
你可能看到它只包含实际值,并为空值打印 null。
切换到库 GitHub - guregu/null: reasonable handling of nullable values 并使用 null.Time 代替 sql.NullTime,使用 null.Float 代替 sql.NullFloat64。它会在 JSON 中将 NULL 值编码为 null。
omsec:
我在 Go 版本中遇到的另一个问题是,“添加/创建”服务也期望那些对象类型的 JSON 结构,如果客户端只提供了值,则无法映射实际的 sql 类型。
我不太明白你在这里想表达什么,你能提供一些例子来说明你的意思吗。
更多关于Golang中如何序列化SQL类型的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go中处理SQL可空类型与JSON序列化的标准方法是实现自定义的MarshalJSON和UnmarshalJSON方法。以下是针对你的Aufgabe结构体的完整解决方案:
import (
"database/sql"
"encoding/json"
"time"
)
type Aufgabe struct {
ID int `json:"id"`
Beschreibung string `json:"beschreibung"`
StatusCode int `json:"statusCode"`
StatusText string `json:"statusText"`
KategorieCode int `json:"kategorieCode"`
KategorieText string `json:"kategorieText"`
ErstelltAm time.Time `json:"erstelltAmTS"`
ZuErledigenBis sql.NullTime `json:"zuErledigenBisTS"`
Kosten sql.NullFloat64 `json:"kosten"`
DauerMin sql.NullInt32 `json:"dauerMin"`
Wichtig bool `json:"wichtig"`
ErledigtAm sql.NullTime `json:"erledigtAmTS"`
}
// MarshalJSON 自定义JSON序列化
func (a Aufgabe) MarshalJSON() ([]byte, error) {
type Alias Aufgabe // 创建别名避免递归调用
// 处理可空字段
var zuErledigenBis interface{}
if a.ZuErledigenBis.Valid {
zuErledigenBis = a.ZuErledigenBis.Time.Format(time.RFC3339)
} else {
zuErledigenBis = nil
}
var kosten interface{}
if a.Kosten.Valid {
kosten = a.Kosten.Float64
} else {
kosten = nil
}
var dauerMin interface{}
if a.DauerMin.Valid {
dauerMin = a.DauerMin.Int32
} else {
dauerMin = nil
}
var erledigtAm interface{}
if a.ErledigtAm.Valid {
erledigtAm = a.ErledigtAm.Time.Format(time.RFC3339)
} else {
erledigtAm = nil
}
// 创建临时结构体进行序列化
aux := &struct {
*Alias
ZuErledigenBis interface{} `json:"zuErledigenBisTS"`
Kosten interface{} `json:"kosten"`
DauerMin interface{} `json:"dauerMin"`
ErledigtAm interface{} `json:"erledigtAmTS"`
ErstelltAm string `json:"erstelltAmTS"`
}{
Alias: (*Alias)(&a),
ZuErledigenBis: zuErledigenBis,
Kosten: kosten,
DauerMin: dauerMin,
ErledigtAm: erledigtAm,
ErstelltAm: a.ErstelltAm.Format(time.RFC3339),
}
return json.Marshal(aux)
}
// UnmarshalJSON 自定义JSON反序列化
func (a *Aufgabe) UnmarshalJSON(data []byte) error {
type Alias Aufgabe
aux := &struct {
ZuErledigenBis *string `json:"zuErledigenBisTS"`
Kosten *float64 `json:"kosten"`
DauerMin *int32 `json:"dauerMin"`
ErledigtAm *string `json:"erledigtAmTS"`
ErstelltAm string `json:"erstelltAmTS"`
*Alias
}{
Alias: (*Alias)(a),
}
if err := json.Unmarshal(data, aux); err != nil {
return err
}
// 处理时间字段
if t, err := time.Parse(time.RFC3339, aux.ErstelltAm); err == nil {
a.ErstelltAm = t
}
// 处理可空时间字段
if aux.ZuErledigenBis != nil {
if t, err := time.Parse(time.RFC3339, *aux.ZuErledigenBis); err == nil {
a.ZuErledigenBis = sql.NullTime{Time: t, Valid: true}
}
}
if aux.ErledigtAm != nil {
if t, err := time.Parse(time.RFC3339, *aux.ErledigtAm); err == nil {
a.ErledigtAm = sql.NullTime{Time: t, Valid: true}
}
}
// 处理可空数值字段
if aux.Kosten != nil {
a.Kosten = sql.NullFloat64{Float64: *aux.Kosten, Valid: true}
}
if aux.DauerMin != nil {
a.DauerMin = sql.NullInt32{Int32: *aux.DauerMin, Valid: true}
}
return nil
}
使用示例:
// 序列化示例
func main() {
aufgabe := Aufgabe{
ID: 5,
Beschreibung: "sdsd",
StatusCode: 2,
StatusText: "gelöscht",
KategorieCode: 2,
KategorieText: "einkauf",
ErstelltAm: time.Now(),
ZuErledigenBis: sql.NullTime{
Time: time.Date(2020, 9, 22, 0, 0, 0, 0, time.UTC),
Valid: true,
},
Kosten: sql.NullFloat64{Valid: false},
DauerMin: sql.NullInt32{Valid: false},
Wichtig: false,
ErledigtAm: sql.NullTime{Valid: false},
}
// 序列化为JSON
jsonData, err := json.Marshal(aufgabe)
if err != nil {
panic(err)
}
fmt.Println(string(jsonData))
// 输出:
// {
// "id": 5,
// "beschreibung": "sdsd",
// "statusCode": 2,
// "statusText": "gelöscht",
// "kategorieCode": 2,
// "kategorieText": "einkauf",
// "erstelltAmTS": "2020-09-15T18:45:39.974309Z",
// "zuErledigenBisTS": "2020-09-22T00:00:00Z",
// "kosten": null,
// "dauerMin": null,
// "wichtig": false,
// "erledigtAmTS": null
// }
// 反序列化示例
jsonStr := `{
"id": 5,
"beschreibung": "sdsd",
"statusCode": 2,
"statusText": "gelöscht",
"kategorieCode": 2,
"kategorieText": "einkauf",
"erstelltAmTS": "2020-09-15T18:45:39.974309Z",
"zuErledigenBisTS": "2020-09-22T00:00:00Z",
"kosten": null,
"dauerMin": 30,
"wichtig": false,
"erledigtAmTS": null
}`
var aufgabe2 Aufgabe
if err := json.Unmarshal([]byte(jsonStr), &aufgabe2); err != nil {
panic(err)
}
fmt.Printf("DauerMin: %v (Valid: %v)\n", aufgabe2.DauerMin.Int32, aufgabe2.DauerMin.Valid)
// 输出:DauerMin: 30 (Valid: true)
}
这个实现确保了:
- 序列化时,有效的SQL可空字段输出实际值,无效字段输出
null - 所有时间字段都格式化为RFC3339格式
- 反序列化时,可以正确解析客户端发送的JSON数据
- 保持了与数据库的兼容性,可以直接使用
sql.Null*类型进行数据库操作

