diff --git a/PR_DESCRIPTION.md b/PR_DESCRIPTION.md new file mode 100644 index 00000000..5cf6903a --- /dev/null +++ b/PR_DESCRIPTION.md @@ -0,0 +1,62 @@ +### What problem does this PR solve? + +Issue Number: Close #2810 + +Problem Summary: +Users have requested an example of how to integrate `json-iterator` with Gin at runtime (without using build tags). While there is documentation, a runnable example project is helpful for understanding the integration points, specifically implementing the `json.Core` interface and replacing `json.API`. + +### What is changed and how does it work? + +- Added a new example under `examples/json-iterator`. +- Implemented `customJsonApi` which wraps `jsoniter.Config` and implements `json.Core`. +- Demonstrates how to replace the default `json.API` with the custom implementation. +- Added a unit test to verify the integration works as expected. +- Added a README for the example explaining how to run and test it. + +### Check List + +Tests + +- [x] Unit test + - Added `examples/json-iterator/json_iterator_test.go` +- [ ] Integration test +- [x] Manual test + - Verified with `curl` locally. +- [ ] No code + +Code changes + +- [ ] Has the configuration change +- [ ] Has HTTP API interfaces changed +- [ ] Has persistent data change + +Side effects + +- [ ] Possible performance regression +- [ ] Increased code complexity +- [ ] Breaking backward compatibility + +Related changes + +- [ ] PR to update [`pingcap/docs`](https://github.com/pingcap/docs)/[`pingcap/docs-cn`](https://github.com/pingcap/docs-cn): +- [ ] PR to update [`pingcap/tiup`](https://github.com/pingcap/tiup): +- [ ] Need to cherry-pick to the release branch + +### How to Test + +1. Navigate to the example directory: + ```bash + cd examples/json-iterator + ``` +2. Run the tests: + ```bash + go test -v + ``` +3. Run the example: + ```bash + go run main.go + ``` +4. Make a request: + ```bash + curl http://localhost:8080/ping + ``` diff --git a/examples/json-iterator/README.md b/examples/json-iterator/README.md new file mode 100644 index 00000000..d0d15829 --- /dev/null +++ b/examples/json-iterator/README.md @@ -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"} +``` diff --git a/examples/json-iterator/json_iterator_test.go b/examples/json-iterator/json_iterator_test.go new file mode 100644 index 00000000..f623a95e --- /dev/null +++ b/examples/json-iterator/json_iterator_test.go @@ -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"]) +} diff --git a/examples/json-iterator/main.go b/examples/json-iterator/main.go new file mode 100644 index 00000000..166101d8 --- /dev/null +++ b/examples/json-iterator/main.go @@ -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") +}