diff --git a/context.go b/context.go index a2384c0e..b5a7885f 100644 --- a/context.go +++ b/context.go @@ -859,7 +859,7 @@ func (c *Context) HTML(code int, name string, obj interface{}) { // WARNING: we recommend to use this only for development purposes since printing pretty JSON is // more CPU and bandwidth consuming. Use Context.JSON() instead. func (c *Context) IndentedJSON(code int, obj interface{}) { - c.Render(code, render.IndentedJSON{Data: obj}) + c.Render(code, render.IndentedJSON{IndentString: c.engine.indentJsonIndentString, Data: obj}) } // SecureJSON serializes the given struct as Secure JSON into the response body. diff --git a/gin.go b/gin.go index 1c2acbc8..a4eeed8a 100644 --- a/gin.go +++ b/gin.go @@ -11,6 +11,7 @@ import ( "net/http" "os" "path" + "strings" "sync" "github.com/gin-gonic/gin/internal/bytesconv" @@ -19,6 +20,9 @@ import ( const defaultMultipartMemory = 32 << 20 // 32 MB +// A space string +var spaceString = string(32) + var ( default404Body = []byte("404 page not found") default405Body = []byte("405 method not allowed") @@ -102,16 +106,17 @@ type Engine struct { // See the PR #1817 and issue #1644 RemoveExtraSlash bool - delims render.Delims - secureJsonPrefix string - HTMLRender render.HTMLRender - FuncMap template.FuncMap - allNoRoute HandlersChain - allNoMethod HandlersChain - noRoute HandlersChain - noMethod HandlersChain - pool sync.Pool - trees methodTrees + delims render.Delims + secureJsonPrefix string + HTMLRender render.HTMLRender + FuncMap template.FuncMap + allNoRoute HandlersChain + allNoMethod HandlersChain + noRoute HandlersChain + noMethod HandlersChain + pool sync.Pool + trees methodTrees + indentJsonIndentString string } var _ IRouter = &Engine{} @@ -145,6 +150,7 @@ func New() *Engine { trees: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, secureJsonPrefix: "while(1);", + indentJsonIndentString: strings.Repeat(spaceString, 4), } engine.RouterGroup.engine = engine engine.pool.New = func() interface{} { @@ -177,6 +183,13 @@ func (engine *Engine) SecureJsonPrefix(prefix string) *Engine { return engine } +// IndentJsonIndentSpaceNum sets the indentJsonIndentString used in Context.IndentedJSON. +// When we use Context.IndentedJSON, we can use custom indentation to render the response. +func (engine *Engine) IndentJsonIndentSpaceNum(spaceNum int) *Engine { + engine.indentJsonIndentString = strings.Repeat(spaceString, spaceNum) + return engine +} + // LoadHTMLGlob loads HTML files identified by glob pattern // and associates the result with HTML renderer. func (engine *Engine) LoadHTMLGlob(pattern string) { diff --git a/gin_test.go b/gin_test.go index 11bdd79c..313b1d09 100644 --- a/gin_test.go +++ b/gin_test.go @@ -6,13 +6,16 @@ package gin import ( "crypto/tls" + "encoding/json" "fmt" "html/template" "io/ioutil" + "math/rand" "net/http" "net/http/httptest" "reflect" "strconv" + "strings" "sync/atomic" "testing" "time" @@ -544,3 +547,35 @@ func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) func handlerTest1(c *Context) {} func handlerTest2(c *Context) {} + +// Engine.IndentJsonIndentSpaceNum +func TestEngine_IndentJsonIndentSpaceNum(t *testing.T) { + + rand.Seed(time.Now().UnixNano()) + spaceNum := rand.Intn(10) + + router := Default() + defaultResponse := H{"name": "test", "age": 20} + router.IndentJsonIndentSpaceNum(spaceNum) + router.GET("/test", func(c *Context) { + c.IndentedJSON(200, defaultResponse) + }) + s := httptest.NewServer(router) + defer s.Close() + + req, err := http.NewRequest("GET", "/test", nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Fatal("error code: ", rr.Code) + } + + except, _ := json.MarshalIndent(defaultResponse, "", strings.Repeat(spaceString, spaceNum)) + + assert.Equal(t, rr.Body.Bytes(), except) + t.Log(rr.Body.String()) +} diff --git a/render/json.go b/render/json.go index 015c0dbb..2c6c4f2b 100644 --- a/render/json.go +++ b/render/json.go @@ -21,6 +21,7 @@ type JSON struct { // IndentedJSON contains the given interface object. type IndentedJSON struct { + IndentString string Data interface{} } @@ -80,7 +81,7 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error { // Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType. func (r IndentedJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) - jsonBytes, err := json.MarshalIndent(r.Data, "", " ") + jsonBytes, err := json.MarshalIndent(r.Data, "", r.IndentString) if err != nil { return err } diff --git a/render/render_test.go b/render/render_test.go index 353c82bb..4a840a0d 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -55,7 +55,7 @@ func TestRenderIndentedJSON(t *testing.T) { "bar": "foo", } - err := (IndentedJSON{data}).Render(w) + err := (IndentedJSON{" ", data}).Render(w) assert.NoError(t, err) assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String()) @@ -67,7 +67,7 @@ func TestRenderIndentedJSONPanics(t *testing.T) { data := make(chan int) // json: unsupported type: chan int - err := (IndentedJSON{data}).Render(w) + err := (IndentedJSON{"", data}).Render(w) assert.Error(t, err) }