Merge 92006a551229eced677ce6dc07e450d4ad64e7ef into d3ffc9985281dcf4d3bef604cce4e662b1a327a6

This commit is contained in:
ljluestc 2026-03-18 15:48:11 +08:00 committed by GitHub
commit d9690e0333
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 149 additions and 6 deletions

View File

@ -830,7 +830,11 @@ func TestUriBinding(t *testing.T) {
}
var not NotSupportStruct
require.Error(t, b.BindUri(m, &not))
assert.Equal(t, map[string]any(nil), not.Name)
require.Error(t, b.BindUri(m, &not))
// Check that if the map is not nil, it is empty (json-iterator may allocate map before error)
if not.Name != nil {
assert.Empty(t, not.Name)
}
}
func TestUriInnerBinding(t *testing.T) {

View File

@ -2138,7 +2138,7 @@ func TestContextBindRequestTooLarge(t *testing.T) {
// https://github.com/goccy/go-json/issues/485
var expectedCode int
switch json.Package {
case "github.com/goccy/go-json":
case "github.com/goccy/go-json", "github.com/json-iterator/go":
expectedCode = http.StatusBadRequest
default:
expectedCode = http.StatusRequestEntityTooLarge

View File

@ -0,0 +1,26 @@
# JSON Iterator Example
This example demonstrates how to integrate [json-iterator/go](https://github.com/json-iterator/go) with Gin to replace the default encoding/json for better performance.
## How it works
Gin supports custom JSON serialization and deserialization logic via the `json.API` variable. By implementing the `json.Core` interface (which includes `Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder` etc.), we can swap out the underlying JSON engine.
## Usage
1. Define your custom configuration using `jsoniter.Config`.
2. Implement the `json.Core` interface wrappers.
3. Assign your custom implementation to `json.API` before creating the Gin engine.
## Run the example
```bash
go run main.go
```
Test it:
```bash
curl http://localhost:8080/ping
# Output: {"message":"pong"}
```

View File

@ -0,0 +1,45 @@
package main
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
ginjson "github.com/gin-gonic/gin/codec/json"
"github.com/stretchr/testify/assert"
)
func TestJsonIterator(t *testing.T) {
// Restore default json api after test
originalAPI := ginjson.API
defer func() {
ginjson.API = originalAPI
}()
// Use custom json api
ginjson.API = customJsonApi{}
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/test", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"hello": "world",
"foo": "bar",
})
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/test", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
// Verify JSON response
var response map[string]string
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, "world", response["hello"])
assert.Equal(t, "bar", response["foo"])
}

View File

@ -0,0 +1,55 @@
package main
import (
"io"
"net/http"
"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()
// customJsonApi 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 with json-iterator
json.API = customJsonApi{}
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run(":8080")
}

View File

@ -64,7 +64,20 @@ func testRequest(t *testing.T, params ...string) {
}
func TestRunEmpty(t *testing.T) {
os.Setenv("PORT", "")
// Listen on a random available port to avoid conflicts
l, err := net.Listen("tcp", "localhost:0")
require.NoError(t, err)
defer l.Close()
addr := l.Addr().String()
_, port, err := net.SplitHostPort(addr)
require.NoError(t, err)
// Close the listener so router.Run() can bind to it (there's a small race here, but better than hardcoded 8080)
// Actually, router.Run() calls http.ListenAndServe which creates its own listener.
// If we close 'l', 'router.Run' can pick it up.
l.Close()
os.Setenv("PORT", port)
router := New()
go func() {
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
@ -72,11 +85,11 @@ func TestRunEmpty(t *testing.T) {
}()
// Wait for server to be ready with exponential backoff
err := waitForServerReady("http://localhost:8080/example", 10)
err = waitForServerReady("http://"+addr+"/example", 10)
require.NoError(t, err, "server should start successfully")
require.Error(t, router.Run(":8080"))
testRequest(t, "http://localhost:8080/example")
require.Error(t, router.Run(":"+port))
testRequest(t, "http://"+addr+"/example")
}
func TestBadTrustedCIDRs(t *testing.T) {