You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Go+Gin+sqlc中实现JSON与模型的自动编解码?

解决sqlc生成模型与Gin BindJSON不兼容的方案

1. 自定义兼容JSON的NullTime类型,让sqlc生成时替换默认类型

既然sqlc生成的sql.NullTime不支持JSON编解码,我们可以自己实现一个兼容的类型,然后让sqlc在生成代码时用这个自定义类型替换标准库的sql.NullTime,这样生成的模型就能直接和Gin的BindJSON配合使用,不需要手动转换。

首先定义自定义的NullTime类型,实现JSON编解码接口:

// types/null_time.go
package types

import (
	"database/sql"
	"encoding/json"
	"time"
)

type NullTime struct {
	sql.NullTime
}

// UnmarshalJSON 实现json.Unmarshaler接口,处理字符串格式的时间
func (nt *NullTime) UnmarshalJSON(data []byte) error {
	var s *string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}
	if s == nil {
		nt.Valid = false
		return nil
	}
	t, err := time.Parse(time.RFC3339, *s)
	if err != nil {
		// 可根据业务需求扩展支持更多时间格式
		return err
	}
	nt.Time = t
	nt.Valid = true
	return nil
}

// MarshalJSON 实现json.Marshaler接口,有效时间转RFC3339字符串,无效则返回null
func (nt NullTime) MarshalJSON() ([]byte, error) {
	if !nt.Valid {
		return json.Marshal(nil)
	}
	return json.Marshal(nt.Time.Format(time.RFC3339))
}

然后修改sqlc的配置文件(sqlc.yaml),添加类型替换规则:

version: "2"
sql:
  - schema: "schema.sql"
    queries: "queries.sql"
    engine: "postgresql" # 根据你的数据库类型调整
    gen:
      go:
        package: "data"
        out: "data"
        overrides:
          - db_type: "timestamp"
            nullable: true
            go_type: "your-module-path/types.NullTime" # 替换为你的自定义类型路径

这样sqlc生成的Author结构体就会自动使用自定义类型:

type Author struct {
    Name         string         `json:"name"`
    Bio          types.NullTime `json:"bio"`
}

之后直接用c.BindJSON(&authorParams)就能正常编解码,返回数据时也无需额外转换。

2. 自定义Gin的JSON绑定器,处理sql.NullTime类型

如果不想改动sqlc的生成配置,也可以自定义Gin的绑定逻辑,在绑定JSON时专门处理sql.NullTime类型。

首先实现自定义绑定器,基于Gin默认JSON绑定器扩展:

// binders/custom_json.go
package binders

import (
	"database/sql"
	"encoding/json"
	"net/http"
	"reflect"
	"time"

	"github.com/gin-gonic/gin/binding"
)

type CustomJSONBinding struct{}

func (CustomJSONBinding) Name() string {
	return "json"
}

func (CustomJSONBinding) Bind(req *http.Request, obj any) error {
	decoder := json.NewDecoder(req.Body)
	// 注册sql.NullTime的自定义解码器
	decoder.RegisterDecoder(reflect.TypeOf(sql.NullTime{}), func(data []byte, v any) error {
		nt := v.(*sql.NullTime)
		var s *string
		if err := json.Unmarshal(data, &s); err != nil {
			return err
		}
		if s == nil {
			nt.Valid = false
			return nil
		}
		t, err := time.Parse(time.RFC3339, *s)
		if err != nil {
			return err
		}
		nt.Time = t
		nt.Valid = true
		return nil
	})
	return decoder.Decode(obj)
}

然后在Gin初始化时替换默认的JSON绑定器:

// main.go
import (
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"your-module-path/binders"
)

func main() {
	r := gin.Default()
	// 替换默认JSON绑定器
	binding.Binders["json"] = binders.CustomJSONBinding{}

	// 注册路由...
	r.Run()
}

这样使用c.BindJSON时会自动处理sql.NullTime类型。如果需要处理响应编码,可通过类型别名给sql.NullTime添加MarshalJSON方法:

// types/null_time.go
package types

import (
	"database/sql"
	"encoding/json"
	"time"
)

type NullTime sql.NullTime

func (nt NullTime) MarshalJSON() ([]byte, error) {
	if !nt.Valid {
		return json.Marshal(nil)
	}
	return json.Marshal(nt.Time.Format(time.RFC3339))
}

// 返回数据时做类型转换即可
// respBio := types.NullTime(data.Author.Bio)

3. 用代码生成工具自动生成转换逻辑

如果以上方案都不想用,可借助mapstructure库自动处理结构体字段映射,减少手动编写转换代码的冗余:

首先安装依赖:

go get github.com/mitchellh/mapstructure

然后在代码中使用:

// controller.go
import (
	"database/sql"
	"reflect"
	"time"

	"github.com/mitchellh/mapstructure"
)

// 绑定请求到临时结构
var apiAuthor map[string]any
if err := c.BindJSON(&apiAuthor); err != nil {
	// 处理错误
}

var authorParams data.InsertFileAuthorParams
config := &mapstructure.DecoderConfig{
	Result: &authorParams,
	DecodeHook: mapstructure.ComposeDecodeHookFunc(
		// 自定义字符串转sql.NullTime的逻辑
		func(f reflect.Type, t reflect.Type, data any) (any, error) {
			if f.Kind() == reflect.String && t == reflect.TypeOf(sql.NullTime{}) {
				s := data.(string)
				tm, err := time.Parse(time.RFC3339, s)
				if err != nil {
					return nil, err
				}
				return sql.NullTime{Time: tm, Valid: true}, nil
			}
			return data, nil
		},
	),
}
decoder, err := mapstructure.NewDecoder(config)
if err != nil {
	// 处理错误
}
if err := decoder.Decode(apiAuthor); err != nil {
	// 处理错误
}

内容的提问来源于stack exchange,提问作者elixirfan2001

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 19:13:14