Skip to content

Commit 024a529

Browse files
authored
Allow custom JSON encoder (like jsoniter) (#105)
1 parent 15b217a commit 024a529

File tree

4 files changed

+90
-2
lines changed

4 files changed

+90
-2
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ r := render.New(render.Options{
104104
HTMLTemplateOption: "missingkey=error", // Sets the option value for HTML templates. See https://pkg.go.dev/html/template#Template.Option for a list of known options.
105105
RequirePartials: true, // Return an error if a template is missing a partial used in a layout.
106106
DisableHTTPErrorRendering: true, // Disables automatic rendering of http.StatusInternalServerError when an error occurs.
107+
JSONEncoder: func(w io.Writer) render.JSONEncoder { // Use jsoniter "github.com/json-iterator"
108+
return jsoniter.NewEncoder(w)
109+
},
107110
})
108111
// ...
109112
~~~
@@ -146,6 +149,7 @@ r := render.New(render.Options{
146149
DisableHTTPErrorRendering: false,
147150
RenderPartialsWithoutPrefix: false,
148151
BufferPool: GenericBufferPool,
152+
JSONEncoder: nil,
149153
})
150154
~~~
151155

engine.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,21 @@ type HTML struct {
3434
bp GenericBufferPool
3535
}
3636

37+
// JSONEncoder match encoding/json.Encoder capabilities.
38+
type JSONEncoder interface {
39+
Encode(v interface{}) error
40+
SetEscapeHTML(on bool)
41+
SetIndent(prefix, indent string)
42+
}
43+
3744
// JSON built-in renderer.
3845
type JSON struct {
3946
Head
4047
Indent bool
4148
UnEscapeHTML bool
4249
Prefix []byte
4350
StreamingJSON bool
51+
NewEncoder func(w io.Writer) JSONEncoder
4452
}
4553

4654
// JSONP built-in renderer.
@@ -114,7 +122,7 @@ func (j JSON) Render(w io.Writer, v interface{}) error {
114122
}
115123

116124
var buf bytes.Buffer
117-
encoder := json.NewEncoder(&buf)
125+
encoder := j.NewEncoder(&buf)
118126
encoder.SetEscapeHTML(!j.UnEscapeHTML)
119127

120128
if j.Indent {
@@ -155,7 +163,7 @@ func (j JSON) renderStreamingJSON(w io.Writer, v interface{}) error {
155163
_, _ = w.Write(j.Prefix)
156164
}
157165

158-
encoder := json.NewEncoder(w)
166+
encoder := j.NewEncoder(w)
159167
encoder.SetEscapeHTML(!j.UnEscapeHTML)
160168

161169
if j.Indent {

render.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package render
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"fmt"
67
"html/template"
78
"io"
@@ -117,6 +118,8 @@ type Options struct {
117118
// BufferPool to use when rendering HTML templates. If none is supplied
118119
// defaults to SizedBufferPool of size 32 with 512KiB buffers.
119120
BufferPool GenericBufferPool
121+
// Custom JSON Encoder. Default to encoding/json.NewEncoder.
122+
JSONEncoder func(w io.Writer) JSONEncoder
120123
}
121124

122125
// HTMLOptions is a struct for overriding some rendering Options for specific HTML call.
@@ -209,6 +212,12 @@ func (r *Render) prepareOptions() {
209212
} else {
210213
r.lock = &emptyLock{}
211214
}
215+
216+
if r.opt.JSONEncoder == nil {
217+
r.opt.JSONEncoder = func(w io.Writer) JSONEncoder {
218+
return json.NewEncoder(w)
219+
}
220+
}
212221
}
213222

214223
func (r *Render) CompileTemplates() {
@@ -526,6 +535,7 @@ func (r *Render) JSON(w io.Writer, status int, v interface{}) error {
526535
Prefix: r.opt.PrefixJSON,
527536
UnEscapeHTML: r.opt.UnEscapeHTML,
528537
StreamingJSON: r.opt.StreamingJSON,
538+
NewEncoder: r.opt.JSONEncoder,
529539
}
530540

531541
return r.Render(w, j, v)

render_json_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package render
22

33
import (
44
"encoding/json"
5+
"io"
56
"math"
67
"net/http"
78
"net/http/httptest"
@@ -13,6 +14,28 @@ type Greeting struct {
1314
Two string `json:"two"`
1415
}
1516

17+
type TestEncoder struct {
18+
JSONEncoder
19+
w io.Writer
20+
}
21+
22+
func (e TestEncoder) NewEncoder(w io.Writer) JSONEncoder {
23+
return TestEncoder{w: w}
24+
}
25+
26+
func (e TestEncoder) Encode(v interface{}) error {
27+
e.w.Write([]byte(e.String()))
28+
return nil
29+
}
30+
31+
func (e TestEncoder) SetEscapeHTML(on bool) {}
32+
33+
func (e TestEncoder) SetIndent(prefix, indent string) {}
34+
35+
func (e TestEncoder) String() string {
36+
return "{\"one\":\"world\",\"two\":\"hello\"}"
37+
}
38+
1639
func TestJSONBasic(t *testing.T) {
1740
render := New()
1841

@@ -346,3 +369,46 @@ func TestJSONDisabledCharset(t *testing.T) {
346369
expect(t, res.Header().Get(ContentType), ContentJSON)
347370
expect(t, res.Body.String(), "{\"one\":\"hello\",\"two\":\"world\"}")
348371
}
372+
373+
func TestJSONEncoder(t *testing.T) {
374+
render := New(Options{
375+
JSONEncoder: TestEncoder{}.NewEncoder,
376+
})
377+
378+
var err error
379+
380+
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
381+
err = render.JSON(w, 299, Greeting{"hello", "world"})
382+
})
383+
384+
res := httptest.NewRecorder()
385+
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
386+
h.ServeHTTP(res, req)
387+
388+
expectNil(t, err)
389+
expect(t, res.Code, 299)
390+
expect(t, res.Header().Get(ContentType), ContentJSON+"; charset=UTF-8")
391+
expect(t, res.Body.String(), TestEncoder{}.String())
392+
}
393+
394+
func TestJSONEncoderStream(t *testing.T) {
395+
render := New(Options{
396+
JSONEncoder: TestEncoder{}.NewEncoder,
397+
StreamingJSON: true,
398+
})
399+
400+
var err error
401+
402+
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
403+
err = render.JSON(w, 299, Greeting{"hello", "world"})
404+
})
405+
406+
res := httptest.NewRecorder()
407+
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
408+
h.ServeHTTP(res, req)
409+
410+
expectNil(t, err)
411+
expect(t, res.Code, 299)
412+
expect(t, res.Header().Get(ContentType), ContentJSON+"; charset=UTF-8")
413+
expect(t, res.Body.String(), TestEncoder{}.String())
414+
}

0 commit comments

Comments
 (0)