diff --git a/context.go b/context.go index d69df70b..10573b38 100644 --- a/context.go +++ b/context.go @@ -23,6 +23,8 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/render" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" ) // Content-Type MIME of the most common data formats. @@ -939,6 +941,12 @@ func (c *Context) JSON(code int, obj interface{}) { c.Render(code, render.JSON{Data: obj}) } +// JSONPB serializes the given proto Message as JSON into the response body. +// It also sets the Content-Type as "application/json". +func (c *Context) JSONPB(code int, obj proto.Message) { + c.Render(code, render.JSONPB{Data: obj, Option: protojson.MarshalOptions{EmitUnpopulated: true}}) +} + // AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string. // It also sets the Content-Type as "application/json". func (c *Context) AsciiJSON(code int, obj interface{}) { diff --git a/render/jsonpb.go b/render/jsonpb.go new file mode 100644 index 00000000..41611a3d --- /dev/null +++ b/render/jsonpb.go @@ -0,0 +1,36 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package render + +import ( + "encoding/json" + "net/http" + + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +// JSONPB contains the given proto.Message object. +type JSONPB struct { + Data proto.Message + datapb json.RawMessage + Option protojson.MarshalOptions +} + +// Render (JSONPB) writes data with custom ContentType. +func (r JSONPB) Render(w http.ResponseWriter) (err error) { + r.datapb, err = r.Option.Marshal(r.Data) + if err != nil { + return err + } + writeContentType(w, jsonContentType) + _, err = w.Write(r.datapb) + return err +} + +// WriteContentType (JSON) writes JSON ContentType. +func (r JSONPB) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonContentType) +} diff --git a/render/render_test.go b/render/render_test.go index e417731a..cf9e9808 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -15,7 +15,9 @@ import ( "testing" testdata "github.com/gin-gonic/gin/testdata/protoexample" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" ) @@ -510,3 +512,46 @@ func TestRenderReaderNoContentLength(t *testing.T) { assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id")) } + +func TestRenderJsonPb(t *testing.T) { + w := httptest.NewRecorder() + reps := []int64{int64(1), int64(2)} + typ := int32(11) + label := "test" + data := &testdata.Test{ + Label: &label, + Type: &typ, + Reps: reps, + Optionalgroup: &testdata.Test_OptionalGroup{RequiredField: &label}, + } + + r := (JSONPB{ + Data: data, + datapb: []byte{}, + Option: protojson.MarshalOptions{ + EmitUnpopulated: true, + }, + }) + r.WriteContentType(w) + result, err := protojson.MarshalOptions{EmitUnpopulated: true}.Marshal(data) + assert.NoError(t, err) + err = r.Render(w) + assert.NoError(t, err) + assert.Equal(t, string(result), w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) + + w1 := httptest.NewRecorder() + r1 := (JSONPB{ + Data: nil, + datapb: []byte{}, + Option: protojson.MarshalOptions{ + EmitUnpopulated: true, + }, + }) + r1.WriteContentType(w) + result2 := "{}" + err1 := r1.Render(w1) + assert.NoError(t, err1) + assert.Equal(t, result2, w1.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type")) +}