Gin 运行时如何实现 json.Core 并替换 json.API 而不用构建标签【免费下载链接】ginGin is a high-performance HTTP web framework written in Go. It provides a Martini-like API but with significantly better performance—up to 40 times faster—thanks to httprouter. Gin is designed for building REST APIs, web applications, and microservices.项目地址: https://gitcode.com/GitHub_Trending/gi/gin如果你的 Gin 项目需要定制 JSON 序列化逻辑——例如换用特定库、开启特定配置HTML 转义、Map 键排序、RawMessage 校验或自定义某类类型的时间格式——传统做法是加构建标签jsoniter、go_json、sonic在编译期切换 codec。另一条文档明确给出的路径是运行时替换自定义一个实现json.Core接口的结构体在引擎启动前把它赋值给json.API全局变量整个过程不需要任何构建标签也不需要修改 gin 源码。先看 Gin 的 JSON codec 是怎么组织的Gin 把 JSON codec 抽象在 codec/json 包中核心是一个接口和一个全局变量// codec/json/api.go // API the json codec in use. var API Core // Core the api for json codec. type Core interface { Marshal(v any) ([]byte, error) Unmarshal(data []byte, v any) error MarshalIndent(v any, prefix, indent string) ([]byte, error) NewEncoder(writer io.Writer) Encoder NewDecoder(reader io.Reader) Decoder }编译期切换由四个文件上的构建标签完成codec/json/json.go构建标签为!jsoniter !go_json !(sonic (linux || windows || darwin))即默认走标准库encoding/json其init()执行API jsonApi{}codec/json/go_json.go标签go_json使用github.com/goccy/go-jsoncodec/json/jsoniter.go标签jsoniter使用github.com/json-iterator/gocodec/json/sonic.go标签sonic (linux || windows || darwin)使用github.com/bytedance/sonic。四条路径最终都落在同一个动作上在init()里给API赋值。运行时替换就是跳过构建标签这一层直接改API指向。需要实现的三个接口自定义结构体必须实现 Core 的五个方法它返回的Encoder和Decoder也要满足 Gin 定义codec/json/api.gotype Encoder interface { SetEscapeHTML(on bool) Encode(v any) error } type Decoder interface { UseNumber() DisallowUnknownFields() Decode(v any) error }有两个调用点会约束你的实现render/json.go 中PureJSON的渲染会调用encoder.SetEscapeHTML(false)所以自定义Encoder必须提供该方法binding/json.go 的decodeJSON只在包级变量binding.EnableDecoderUseNumber或binding.EnableDecoderDisallowUnknownFields为true时才调用decoder.UseNumber()/decoder.DisallowUnknownFields()因此自定义Decoder也要实现这两个方法不实现就没有对应行为。实现自定义 codec 并替换 json.API以下是 docs/doc.md “Custom json codec at runtime” 一节给出的完整示例。它用json-iterator/go的配置EscapeHTML、SortMapKeys、ValidateJsonRawMessage经Froze()冻结后使用承载序列化逻辑前置条件是你的模块引入了该依赖package main import ( io github.com/gin-gonic/gin github.com/gin-gonic/gin/codec/json jsoniter github.com/json-iterator/go ) var customConfig jsoniter.Config{ EscapeHTML: true, SortMapKeys: true, ValidateJsonRawMessage: true, }.Froze() // implement api.JsonApi type customJsonApi struct { } func (j customJsonApi) Marshal(v any) ([]byte, error) { return customConfig.Marshal(v) } func (j customJsonApi) Unmarshal(data []byte, v any) error { return customConfig.Unmarshal(data, v) } func (j customJsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) { return customConfig.MarshalIndent(v, prefix, indent) } func (j customJsonApi) NewEncoder(writer io.Writer) json.Encoder { return customConfig.NewEncoder(writer) } func (j customJsonApi) NewDecoder(reader io.Reader) json.Decoder { return customConfig.NewDecoder(reader) } func main() { //Replace the default json api json.API customJsonApi{} //Start your gin engine router : gin.Default() router.Run(:8080) }注意赋值的位置文档要求json.API customJsonApi{}必须发生在引擎启动之前“Before your engine starts”即先替换、再gin.Default()与router.Run(:8080)。替换后生效的范围覆盖 Gin 内部所有 JSON 出入口请求绑定binding/json.go 的decodeJSON通过json.API.NewDecoder(r)解析请求体之后走校验表单中的 JSON 字段binding/form_mapping.go 用json.API.Unmarshal解码响应渲染render/json.go 中JSON、IndentedJSON、SecureJSON、JsonpJSON、AsciiJSON、PureJSON分别走json.API.Marshal/MarshalIndent/NewEncoder错误序列化errors.go 中Error.JSON()用json.API.Marshal(msg.JSON())生成错误对象。也就是说一处赋值后绑定与渲染两侧都换成你的逻辑不存在只改一半的情况。验证替换是否生效仓库中的 binding/json_test.go 的TestCustomJsonCodec展示了完整的验证模式可以照搬到自己的项目里作为测试func TestCustomJsonCodec(t *testing.T) { // Restore json encoding configuration after testing oldMarshal : json.API defer func() { json.API oldMarshal }() // Custom json api json.API customJsonApi{} // test decode json obj : customReq{} err : jsonBinding{}.BindBody([]byte({time_empty:null,time_struct: 2001-12-05 10:01:02.345,time_nil:null,time_pointer:2002-12-05 10:01:02.345}), obj) require.NoError(t, err) assert.Equal(t, zeroTime, obj.TimeEmpty) assert.Equal(t, time.Date(2001, 12, 5, 10, 1, 2, 345000000, time.Local), obj.TimeStruct) assert.Nil(t, obj.TimeNil) assert.Equal(t, time.Date(2002, 12, 5, 10, 1, 2, 345000000, time.Local), *obj.TimePointer) // test encode json w : httptest.NewRecorder() err2 : (render.PureJSON{Data: obj}).Render(w) require.NoError(t, err2) assert.JSONEq(t, {\time_empty\:null,\time_struct\:\2001-12-05 10:01:02.345\,\time_nil\:null,\time_pointer\:\2002-12-05 10:01:02.345\}\n, w.Body.String()) assert.Equal(t, application/json; charsetutf-8, w.Header().Get(Content-Type)) }该测试的判定标准来自文档中的测试代码属示例结果解码侧jsonBinding{}.BindBody能按自定义逻辑解析请求体字段值与预期一致编码侧render.PureJSON{}.Render(w)输出的响应体与预期 JSON 相等assert.JSONEq且Content-Type为application/json; charsetutf-8。测试文件里还给出了一个更有说服力的定制例子通过customConfig.RegisterExtension注册TimeEx/TimePointerEx扩展binding/json_test.go让time.Time按2006-01-02 15:04:05.000本地时区格式序列化/反序列化、零值输出为null。如果你的需求正是这类类型级定制这个扩展写法可以直接参考。另外注意测试开头保存并defer恢复原json.API的写法json.API是包级全局变量在测试中改动它会影响同包其他测试验证完务必还原。限制与边界赋值时机是硬约束文档要求替换发生在引擎启动之前先跑起来再改json.API的行为没有文档支撑自定义Decoder的UseNumber/DisallowUnknownFields只有在 binding 的两个全局开关置为true时才被调用实现里不要假设它们一定被触发运行时替换只影响codec/json这一个包提供的 codec 路径如果你本来就是用构建标签jsoniter、go_json、sonic编译的运行时赋值会把编译期选定的实现覆盖掉反之两者指向同一变量、后赋值者生效。【免费下载链接】ginGin is a high-performance HTTP web framework written in Go. It provides a Martini-like API but with significantly better performance—up to 40 times faster—thanks to httprouter. Gin is designed for building REST APIs, web applications, and microservices.项目地址: https://gitcode.com/GitHub_Trending/gi/gin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考