Skip to content

Commit

Permalink
Allow custom JSON encoder (like jsoniter) (#105)
Browse files Browse the repository at this point in the history
  • Loading branch information
SlIdE42 authored Sep 28, 2024
1 parent 15b217a commit 024a529
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 2 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ r := render.New(render.Options{
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.
RequirePartials: true, // Return an error if a template is missing a partial used in a layout.
DisableHTTPErrorRendering: true, // Disables automatic rendering of http.StatusInternalServerError when an error occurs.
JSONEncoder: func(w io.Writer) render.JSONEncoder { // Use jsoniter "github.com/json-iterator"
return jsoniter.NewEncoder(w)
},
})
// ...
~~~
Expand Down Expand Up @@ -146,6 +149,7 @@ r := render.New(render.Options{
DisableHTTPErrorRendering: false,
RenderPartialsWithoutPrefix: false,
BufferPool: GenericBufferPool,
JSONEncoder: nil,
})
~~~

Expand Down
12 changes: 10 additions & 2 deletions engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,21 @@ type HTML struct {
bp GenericBufferPool
}

// JSONEncoder match encoding/json.Encoder capabilities.
type JSONEncoder interface {
Encode(v interface{}) error
SetEscapeHTML(on bool)
SetIndent(prefix, indent string)
}

// JSON built-in renderer.
type JSON struct {
Head
Indent bool
UnEscapeHTML bool
Prefix []byte
StreamingJSON bool
NewEncoder func(w io.Writer) JSONEncoder
}

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

var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
encoder := j.NewEncoder(&buf)
encoder.SetEscapeHTML(!j.UnEscapeHTML)

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

encoder := json.NewEncoder(w)
encoder := j.NewEncoder(w)
encoder.SetEscapeHTML(!j.UnEscapeHTML)

if j.Indent {
Expand Down
10 changes: 10 additions & 0 deletions render.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package render

import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"io"
Expand Down Expand Up @@ -117,6 +118,8 @@ type Options struct {
// BufferPool to use when rendering HTML templates. If none is supplied
// defaults to SizedBufferPool of size 32 with 512KiB buffers.
BufferPool GenericBufferPool
// Custom JSON Encoder. Default to encoding/json.NewEncoder.
JSONEncoder func(w io.Writer) JSONEncoder
}

// HTMLOptions is a struct for overriding some rendering Options for specific HTML call.
Expand Down Expand Up @@ -209,6 +212,12 @@ func (r *Render) prepareOptions() {
} else {
r.lock = &emptyLock{}
}

if r.opt.JSONEncoder == nil {
r.opt.JSONEncoder = func(w io.Writer) JSONEncoder {
return json.NewEncoder(w)
}
}
}

func (r *Render) CompileTemplates() {
Expand Down Expand Up @@ -526,6 +535,7 @@ func (r *Render) JSON(w io.Writer, status int, v interface{}) error {
Prefix: r.opt.PrefixJSON,
UnEscapeHTML: r.opt.UnEscapeHTML,
StreamingJSON: r.opt.StreamingJSON,
NewEncoder: r.opt.JSONEncoder,
}

return r.Render(w, j, v)
Expand Down
66 changes: 66 additions & 0 deletions render_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package render

import (
"encoding/json"
"io"
"math"
"net/http"
"net/http/httptest"
Expand All @@ -13,6 +14,28 @@ type Greeting struct {
Two string `json:"two"`
}

type TestEncoder struct {
JSONEncoder
w io.Writer
}

func (e TestEncoder) NewEncoder(w io.Writer) JSONEncoder {

Check failure on line 22 in render_json_test.go

View workflow job for this annotation

GitHub Actions / golangci

NewEncoder returns interface (github.com/unrolled/render.JSONEncoder) (ireturn)
return TestEncoder{w: w}
}

func (e TestEncoder) Encode(v interface{}) error {

Check warning on line 26 in render_json_test.go

View workflow job for this annotation

GitHub Actions / golangci

unused-parameter: parameter 'v' seems to be unused, consider removing or renaming it as _ (revive)
e.w.Write([]byte(e.String()))

Check failure on line 27 in render_json_test.go

View workflow job for this annotation

GitHub Actions / golangci

Error return value of `e.w.Write` is not checked (errcheck)
return nil

Check failure on line 28 in render_json_test.go

View workflow job for this annotation

GitHub Actions / golangci

return with no blank line before (nlreturn)
}

func (e TestEncoder) SetEscapeHTML(on bool) {}

Check warning on line 31 in render_json_test.go

View workflow job for this annotation

GitHub Actions / golangci

unused-parameter: parameter 'on' seems to be unused, consider removing or renaming it as _ (revive)

func (e TestEncoder) SetIndent(prefix, indent string) {}

Check warning on line 33 in render_json_test.go

View workflow job for this annotation

GitHub Actions / golangci

unused-parameter: parameter 'prefix' seems to be unused, consider removing or renaming it as _ (revive)

func (e TestEncoder) String() string {
return "{\"one\":\"world\",\"two\":\"hello\"}"
}

func TestJSONBasic(t *testing.T) {
render := New()

Expand Down Expand Up @@ -346,3 +369,46 @@ func TestJSONDisabledCharset(t *testing.T) {
expect(t, res.Header().Get(ContentType), ContentJSON)
expect(t, res.Body.String(), "{\"one\":\"hello\",\"two\":\"world\"}")
}

func TestJSONEncoder(t *testing.T) {
render := New(Options{
JSONEncoder: TestEncoder{}.NewEncoder,
})

var err error

h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSON(w, 299, Greeting{"hello", "world"})
})

res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
h.ServeHTTP(res, req)

expectNil(t, err)
expect(t, res.Code, 299)
expect(t, res.Header().Get(ContentType), ContentJSON+"; charset=UTF-8")
expect(t, res.Body.String(), TestEncoder{}.String())
}

func TestJSONEncoderStream(t *testing.T) {
render := New(Options{
JSONEncoder: TestEncoder{}.NewEncoder,
StreamingJSON: true,
})

var err error

h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSON(w, 299, Greeting{"hello", "world"})
})

res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
h.ServeHTTP(res, req)

expectNil(t, err)
expect(t, res.Code, 299)
expect(t, res.Header().Get(ContentType), ContentJSON+"; charset=UTF-8")
expect(t, res.Body.String(), TestEncoder{}.String())
}

0 comments on commit 024a529

Please sign in to comment.