Repository: gin-gonic/gin
Pull request #1546 (2018-09-14 06:56:43)

use encode replace json marshal increase json encoder speed
```go
func WriteJSON(w http.ResponseWriter, obj interface{}) error {
	writeContentType(w, jsonContentType)
	jsonBytes, err := json.Marshal(obj)
	if err != nil {
		return err
	}
	w.Write(jsonBytes)
	return nil
}

func WriteJSON(w http.ResponseWriter, obj interface{}) error {
	writeContentType(w, jsonContentType)
	encoder := json.NewEncoder(w)
	err := encoder.Encode(&obj)
	if err != nil {
		return err
	}
	return nil
}
```

thinkerou: @itcloudy please fix unit test error, and can you give one link or doc about compare encode with marshal? thanks!
appleboy: @itcloudy build fail
codecov[bot]: # [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/1546?src=pr&el=h1) Report
> Merging [#1546](https://codecov.io/gh/gin-gonic/gin/pull/1546?src=pr&el=desc) into [master](https://codecov.io/gh/gin-gonic/gin/commit/057f63b1bb1cca059173363d10c3de9512ee1110?src=pr&el=desc) will **increase** coverage by `0.11%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/gin-gonic/gin/pull/1546/graphs/tree.svg?width=650&token=Qpqxhwery8&height=150&src=pr)](https://codecov.io/gh/gin-gonic/gin/pull/1546?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1546      +/-   ##
==========================================
+ Coverage   98.62%   98.74%   +0.11%     
==========================================
  Files          41       38       -3     
  Lines        2112     2143      +31     
==========================================
+ Hits         2083     2116      +33     
+ Misses         18       15       -3     
- Partials       11       12       +1
```


| [Impacted Files](https://codecov.io/gh/gin-gonic/gin/pull/1546?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [render/json.go](https://codecov.io/gh/gin-gonic/gin/pull/1546/diff?src=pr&el=tree#diff-cmVuZGVyL2pzb24uZ28=) | `87.34% <ø> (+0.85%)` | :arrow_up: |
| [response\_writer.go](https://codecov.io/gh/gin-gonic/gin/pull/1546/diff?src=pr&el=tree#diff-cmVzcG9uc2Vfd3JpdGVyLmdv) | `95.23% <0%> (-4.77%)` | :arrow_down: |
| [context.go](https://codecov.io/gh/gin-gonic/gin/pull/1546/diff?src=pr&el=tree#diff-Y29udGV4dC5nbw==) | `98.36% <0%> (-0.02%)` | :arrow_down: |
| [binding/form.go](https://codecov.io/gh/gin-gonic/gin/pull/1546/diff?src=pr&el=tree#diff-YmluZGluZy9mb3JtLmdv) | `100% <0%> (ø)` | :arrow_up: |
| [render/redirect.go](https://codecov.io/gh/gin-gonic/gin/pull/1546/diff?src=pr&el=tree#diff-cmVuZGVyL3JlZGlyZWN0Lmdv) | `100% <0%> (ø)` | :arrow_up: |
| [binding/form\_mapping.go](https://codecov.io/gh/gin-gonic/gin/pull/1546/diff?src=pr&el=tree#diff-YmluZGluZy9mb3JtX21hcHBpbmcuZ28=) | `100% <0%> (ø)` | :arrow_up: |
| [recovery.go](https://codecov.io/gh/gin-gonic/gin/pull/1546/diff?src=pr&el=tree#diff-cmVjb3ZlcnkuZ28=) | `100% <0%> (ø)` | :arrow_up: |
| [logger.go](https://codecov.io/gh/gin-gonic/gin/pull/1546/diff?src=pr&el=tree#diff-bG9nZ2VyLmdv) | `100% <0%> (ø)` | :arrow_up: |
| [binding/binding.go](https://codecov.io/gh/gin-gonic/gin/pull/1546/diff?src=pr&el=tree#diff-YmluZGluZy9iaW5kaW5nLmdv) | `100% <0%> (ø)` | :arrow_up: |
| [render/json\_17.go](https://codecov.io/gh/gin-gonic/gin/pull/1546/diff?src=pr&el=tree#diff-cmVuZGVyL2pzb25fMTcuZ28=) | | |
| ... and [5 more](https://codecov.io/gh/gin-gonic/gin/pull/1546/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/gin-gonic/gin/pull/1546?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/1546?src=pr&el=footer). Last update [057f63b...faf25b0](https://codecov.io/gh/gin-gonic/gin/pull/1546?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/render/json.go b/render/json.go
index 18f27fa..2b07cba 100644
--- a/render/json.go
+++ b/render/json.go
@@ -68,11 +68,8 @@ func (r JSON) WriteContentType(w http.ResponseWriter) {
 // WriteJSON marshals the given interface object and writes it with custom ContentType.
 func WriteJSON(w http.ResponseWriter, obj interface{}) error {
 	writeContentType(w, jsonContentType)
-	jsonBytes, err := json.Marshal(obj)
-	if err != nil {
-		return err
-	}
-	_, err = w.Write(jsonBytes)
+	encoder := json.NewEncoder(w)
+	err := encoder.Encode(&obj)
 	return err
 }
 


Test patch:
diff --git a/context_test.go b/context_test.go
index 490e449..e8dcd3d 100644
--- a/context_test.go
+++ b/context_test.go
@@ -660,7 +660,7 @@ func TestContextRenderJSON(t *testing.T) {
 	c.JSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
 
 	assert.Equal(t, http.StatusCreated, w.Code)
-	assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String())
+	assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}\n", w.Body.String())
 	assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
 }
 
@@ -688,7 +688,7 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) {
 	c.JSONP(http.StatusCreated, H{"foo": "bar"})
 
 	assert.Equal(t, http.StatusCreated, w.Code)
-	assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
+	assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String())
 	assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
 }
 
@@ -714,7 +714,7 @@ func TestContextRenderAPIJSON(t *testing.T) {
 	c.JSON(http.StatusCreated, H{"foo": "bar"})
 
 	assert.Equal(t, http.StatusCreated, w.Code)
-	assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
+	assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String())
 	assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type"))
 }
 
@@ -1117,7 +1117,7 @@ func TestContextNegotiationWithJSON(t *testing.T) {
 	})
 
 	assert.Equal(t, http.StatusOK, w.Code)
-	assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
+	assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String())
 	assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
 }
 
@@ -1281,7 +1281,7 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
 	_, err := buf.ReadFrom(w.Body)
 	assert.NoError(t, err)
 	jsonStringBody := buf.String()
-	assert.Equal(t, fmt.Sprint(`{"foo":"fooValue","bar":"barValue"}`), jsonStringBody)
+	assert.Equal(t, fmt.Sprint("{\"foo\":\"fooValue\",\"bar\":\"barValue\"}\n"), jsonStringBody)
 }
 
 func TestContextError(t *testing.T) {
diff --git a/logger_test.go b/logger_test.go
index 56bb3a0..9177e1d 100644
--- a/logger_test.go
+++ b/logger_test.go
@@ -369,15 +369,15 @@ func TestErrorLogger(t *testing.T) {
 
 	w := performRequest(router, "GET", "/error")
 	assert.Equal(t, http.StatusOK, w.Code)
-	assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String())
+	assert.Equal(t, "{\"error\":\"this is an error\"}\n", w.Body.String())
 
 	w = performRequest(router, "GET", "/abort")
 	assert.Equal(t, http.StatusUnauthorized, w.Code)
-	assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String())
+	assert.Equal(t, "{\"error\":\"no authorized\"}\n", w.Body.String())
 
 	w = performRequest(router, "GET", "/print")
 	assert.Equal(t, http.StatusInternalServerError, w.Code)
-	assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
+	assert.Equal(t, "hola!{\"error\":\"this is an error\"}\n", w.Body.String())
 }
 
 func TestLoggerWithWriterSkippingPaths(t *testing.T) {
diff --git a/middleware_test.go b/middleware_test.go
index fca1c53..2ae9e88 100644
--- a/middleware_test.go
+++ b/middleware_test.go
@@ -246,5 +246,5 @@ func TestMiddlewareWrite(t *testing.T) {
 	w := performRequest(router, "GET", "/")
 
 	assert.Equal(t, http.StatusBadRequest, w.Code)
-	assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))
+	assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}\n{\"foo\":\"bar\"}\nevent:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))
 }
diff --git a/render/render_test.go b/render/render_test.go
index 3aa5dbc..9d7eaee 100644
--- a/render/render_test.go
+++ b/render/render_test.go
@@ -62,7 +62,7 @@ func TestRenderJSON(t *testing.T) {
 	err := (JSON{data}).Render(w)
 
 	assert.NoError(t, err)
-	assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String())
+	assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}\n", w.Body.String())
 	assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
 }