Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup custom marshaler #146

Merged
merged 4 commits into from
May 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 183 additions & 60 deletions examples/examplepb/a_bit_of_everything.pb.go

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions examples/examplepb/a_bit_of_everything.proto
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ message ABitOfEverything {
sint32 sint32_value = 17;
sint64 sint64_value = 18;
repeated string repeated_string_value = 19;

oneof oneof_value {
EmptyMessage oneof_empty = 20;
string oneof_string = 21;
}
map<string, NumericEnum> map_value = 22;
}

message EmptyMessage {
Expand Down
37 changes: 37 additions & 0 deletions runtime/marshal_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package runtime

import (
"encoding/json"
"io"
)

// JSONBuiltin is a Marshaler which marshals/unmarshals into/from JSON
// with the standard "encoding/json" package of Golang.
// Although it is generally faster for simple proto messages than JSONPb,
// it does not support advanced features of protobuf, e.g. map, oneof, ....
type JSONBuiltin struct{}

// ContentType always Returns "application/json".
func (*JSONBuiltin) ContentType() string {
return "application/json"
}

// Marshal marshals "v" into JSON
func (j *JSONBuiltin) Marshal(v interface{}) ([]byte, error) {
return json.Marshal(v)
}

// Unmarshal unmarshals JSON data into "v".
func (j *JSONBuiltin) Unmarshal(data []byte, v interface{}) error {
return json.Unmarshal(data, v)
}

// NewDecoder returns a Decoder which reads JSON stream from "r".
func (j *JSONBuiltin) NewDecoder(r io.Reader) Decoder {
return json.NewDecoder(r)
}

// NewEncoder returns an Encoder which writes JSON stream into "w".
func (j *JSONBuiltin) NewEncoder(w io.Writer) Encoder {
return json.NewEncoder(w)
}
198 changes: 198 additions & 0 deletions runtime/marshal_json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package runtime_test

import (
"bytes"
"encoding/json"
"reflect"
"strings"
"testing"

"github.com/gengo/grpc-gateway/examples/examplepb"
"github.com/gengo/grpc-gateway/runtime"
)

func TestJSONBuiltinMarshal(t *testing.T) {
var m runtime.JSONBuiltin
msg := examplepb.SimpleMessage{
Id: "foo",
}

buf, err := m.Marshal(&msg)
if err != nil {
t.Errorf("m.Marshal(%v) failed with %v; want success", &msg, err)
}

var got examplepb.SimpleMessage
if err := json.Unmarshal(buf, &got); err != nil {
t.Errorf("json.Unmarshal(%q, &got) failed with %v; want success", buf, err)
}
if want := msg; !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want %v", &got, &want)
}
}

func TestJSONBuiltinMarshalPrimitive(t *testing.T) {
var m runtime.JSONBuiltin
for _, v := range []interface{}{
"",
"foo",
1,
0,
-1,
-0.0,
1.5,
} {
buf, err := m.Marshal(v)
if err != nil {
t.Errorf("m.Marshal(%v) failed with %v; want success", v, err)
}

dest := reflect.New(reflect.TypeOf(v))
if err := json.Unmarshal(buf, dest.Interface()); err != nil {
t.Errorf("json.Unmarshal(%q, unmarshaled) failed with %v; want success", buf, err)
}
if got, want := dest.Elem().Interface(), v; !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want %v", &got, &want)
}
}
}

func TestJSONBuiltinsnmarshal(t *testing.T) {
var (
m runtime.JSONBuiltin
got examplepb.SimpleMessage

data = []byte(`{"id": "foo"}`)
)
if err := m.Unmarshal(data, &got); err != nil {
t.Errorf("m.Unmarshal(%q, &got) failed with %v; want success", data, err)
}

want := examplepb.SimpleMessage{
Id: "foo",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want = %v", &got, &want)
}
}

func TestJSONBuiltinUnmarshalPrimitive(t *testing.T) {
var m runtime.JSONBuiltin
for _, spec := range []struct {
data string
want interface{}
}{
{data: `""`, want: ""},
{data: `"foo"`, want: "foo"},
{data: `1`, want: 1},
{data: `0`, want: 0},
{data: `-1`, want: -1},
{data: `-0.0`, want: -0.0},
{data: `1.5`, want: 1.5},
} {
dest := reflect.New(reflect.TypeOf(spec.want))
if err := m.Unmarshal([]byte(spec.data), dest.Interface()); err != nil {
t.Errorf("m.Unmarshal(%q, dest) failed with %v; want success", spec.data, err)
}

if got, want := dest.Elem().Interface(), spec.want; !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want = %v", got, want)
}
}
}

func TestJSONBuiltinEncoder(t *testing.T) {
var m runtime.JSONBuiltin
msg := examplepb.SimpleMessage{
Id: "foo",
}

var buf bytes.Buffer
enc := m.NewEncoder(&buf)
if err := enc.Encode(&msg); err != nil {
t.Errorf("enc.Encode(%v) failed with %v; want success", &msg, err)
}

var got examplepb.SimpleMessage
if err := json.Unmarshal(buf.Bytes(), &got); err != nil {
t.Errorf("json.Unmarshal(%q, &got) failed with %v; want success", buf.String(), err)
}
if want := msg; !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want %v", &got, &want)
}
}

func TestJSONBuiltinEncoderPrimitive(t *testing.T) {
var m runtime.JSONBuiltin
for _, v := range []interface{}{
"",
"foo",
1,
0,
-1,
-0.0,
1.5,
} {
var buf bytes.Buffer
enc := m.NewEncoder(&buf)
if err := enc.Encode(v); err != nil {
t.Errorf("enc.Encode(%v) failed with %v; want success", v, err)
}

dest := reflect.New(reflect.TypeOf(v))
if err := json.Unmarshal(buf.Bytes(), dest.Interface()); err != nil {
t.Errorf("json.Unmarshal(%q, unmarshaled) failed with %v; want success", buf.String(), err)
}
if got, want := dest.Elem().Interface(), v; !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want %v", &got, &want)
}
}
}

func TestJSONBuiltinDecoder(t *testing.T) {
var (
m runtime.JSONBuiltin
got examplepb.SimpleMessage

data = `{"id": "foo"}`
)
r := strings.NewReader(data)
dec := m.NewDecoder(r)
if err := dec.Decode(&got); err != nil {
t.Errorf("m.Unmarshal(&got) failed with %v; want success", err)
}

want := examplepb.SimpleMessage{
Id: "foo",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want = %v", &got, &want)
}
}

func TestJSONBuiltinDecoderPrimitive(t *testing.T) {
var m runtime.JSONBuiltin
for _, spec := range []struct {
data string
want interface{}
}{
{data: `""`, want: ""},
{data: `"foo"`, want: "foo"},
{data: `1`, want: 1},
{data: `0`, want: 0},
{data: `-1`, want: -1},
{data: `-0.0`, want: -0.0},
{data: `1.5`, want: 1.5},
} {
r := strings.NewReader(spec.data)
dec := m.NewDecoder(r)
dest := reflect.New(reflect.TypeOf(spec.want))
if err := dec.Decode(dest.Interface()); err != nil {
t.Errorf("dec.Decode(dest) failed with %v; want success; data=%q", err, spec.data)
}

if got, want := dest.Elem().Interface(), spec.want; !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want = %v", got, want)
}
}
}
117 changes: 117 additions & 0 deletions runtime/marshal_jsonpb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package runtime

import (
"bytes"
"encoding/json"
"errors"
"io"

"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
)

// JSONPb is a Marshaler which marshals/unmarshals into/from JSON
// with the "github.com/golang/protobuf/jsonpb".
// It supports fully functionality of protobuf unlike JSONBuiltin.
type JSONPb struct {
// Whether to render enum values as integers, as opposed to string values.
EnumsAsInts bool

// Whether to render fields with zero values.
EmitDefaults bool

// A string to indent each level by. The presence of this field will
// also cause a space to appear between the field separator and
// value, and for newlines to be appear between fields and array
// elements.
Indent string

// Whether to use the original (.proto) name for fields.
OrigName bool
}

// ContentType always returns "application/json".
func (*JSONPb) ContentType() string {
return "application/json"
}

// Marshal marshals "v" into JSON
// Currently it can marshal only proto.Message.
// TODO(yugui) Support fields of primitive types in a message.
func (j *JSONPb) Marshal(v interface{}) ([]byte, error) {
m := &jsonpb.Marshaler{
EnumsAsInts: j.EnumsAsInts,
EmitDefaults: j.EmitDefaults,
Indent: j.Indent,
OrigName: j.OrigName,
}
p, ok := v.(proto.Message)
if !ok {
return nil, errors.New("interface is not proto.Message")
}

var buf bytes.Buffer
if err := m.Marshal(&buf, p); err != nil {
return nil, err
}
return buf.Bytes(), nil

}

// Unmarshal unmarshals JSON "data" into "v"
// Currently it can marshal only proto.Message.
// TODO(yugui) Support fields of primitive types in a message.
func (j *JSONPb) Unmarshal(data []byte, v interface{}) error {
r := bytes.NewReader(data)
p, ok := v.(proto.Message)
if !ok {
return errors.New("interface is not proto.Message")
}
return jsonpb.Unmarshal(r, p)
}

// NewDecoder returns a Decoder which reads JSON stream from "r".
func (j *JSONPb) NewDecoder(r io.Reader) Decoder {
return &jsonPbDecoder{decoder: json.NewDecoder(r)}
}

// NewEncoder returns an Encoder which writes JSON stream into "w".
func (j *JSONPb) NewEncoder(w io.Writer) Encoder {
m := &jsonpb.Marshaler{
EnumsAsInts: j.EnumsAsInts,
EmitDefaults: j.EmitDefaults,
Indent: j.Indent,
OrigName: j.OrigName,
}

return &jsonPbEncoder{
marshal: m,
writer: w,
}
}

type jsonPbDecoder struct {
decoder *json.Decoder
}

func (j *jsonPbDecoder) Decode(v interface{}) error {
p, ok := v.(proto.Message)
if !ok {
return errors.New("interface is not proto.Message")
}

return jsonpb.UnmarshalNext(j.decoder, p)
}

type jsonPbEncoder struct {
marshal *jsonpb.Marshaler
writer io.Writer
}

func (j *jsonPbEncoder) Encode(v interface{}) error {
p, ok := v.(proto.Message)
if !ok {
return errors.New("interface is not proto.Message")
}
return j.marshal.Marshal(j.writer, p)
}
Loading