From 3ea4cd3cf3b3de913199e6ce431d5dcae555321c Mon Sep 17 00:00:00 2001 From: Josh Humphries Date: Wed, 2 May 2018 14:24:30 -0400 Subject: [PATCH] JSONPb marshaler panics if input is nil interface --- runtime/marshal_json_test.go | 19 +++++++++++++++++-- runtime/marshal_jsonpb.go | 7 ++++--- runtime/marshal_jsonpb_test.go | 18 ++++++++---------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/runtime/marshal_json_test.go b/runtime/marshal_json_test.go index 6ab9699e48c..1d226d53665 100644 --- a/runtime/marshal_json_test.go +++ b/runtime/marshal_json_test.go @@ -84,7 +84,7 @@ func TestJSONBuiltinsnmarshal(t *testing.T) { func TestJSONBuiltinUnmarshalField(t *testing.T) { var m runtime.JSONBuiltin for _, fixt := range builtinFieldFixtures { - dest := reflect.New(reflect.TypeOf(fixt.data)) + dest := alloc(reflect.TypeOf(fixt.data)) if err := m.Unmarshal([]byte(fixt.json), dest.Interface()); err != nil { t.Errorf("m.Unmarshal(%q, dest) failed with %v; want success", fixt.json, err) } @@ -95,6 +95,14 @@ func TestJSONBuiltinUnmarshalField(t *testing.T) { } } +func alloc(t reflect.Type) reflect.Value { + if t == nil { + return reflect.ValueOf(new(interface{})) + } else { + return reflect.New(t) + } +} + func TestJSONBuiltinUnmarshalFieldKnownErrors(t *testing.T) { var m runtime.JSONBuiltin for _, fixt := range builtinKnownErrors { @@ -167,7 +175,7 @@ func TestJSONBuiltinDecoderFields(t *testing.T) { for _, fixt := range builtinFieldFixtures { r := strings.NewReader(fixt.json) dec := m.NewDecoder(r) - dest := reflect.New(reflect.TypeOf(fixt.data)) + dest := alloc(reflect.TypeOf(fixt.data)) if err := dec.Decode(dest.Interface()); err != nil { t.Errorf("dec.Decode(dest) failed with %v; want success; data = %q", err, fixt.json) } @@ -204,6 +212,13 @@ var ( {data: (*string)(nil), json: "null"}, {data: new(empty.Empty), json: "{}"}, {data: examplepb.NumericEnum_ONE, json: "1"}, + {data: nil, json: "null"}, + {data: (*string)(nil), json: "null"}, + {data: []interface{}{nil, "foo", -1.0, 1.234, true}, json: `[null,"foo",-1,1.234,true]`}, + { + data: map[string]interface{}{"bar": nil, "baz": -1.0, "fiz": 1.234, "foo": true}, + json: `{"bar":null,"baz":-1,"fiz":1.234,"foo":true}`, + }, { data: (*examplepb.NumericEnum)(proto.Int32(int32(examplepb.NumericEnum_ONE))), json: "1", diff --git a/runtime/marshal_jsonpb.go b/runtime/marshal_jsonpb.go index 0a0d130bba6..2a90a4403cf 100644 --- a/runtime/marshal_jsonpb.go +++ b/runtime/marshal_jsonpb.go @@ -21,9 +21,7 @@ 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. +// Marshal marshals "v" into JSON. func (j *JSONPb) Marshal(v interface{}) ([]byte, error) { if _, ok := v.(proto.Message); !ok { return j.marshalNonProtoField(v) @@ -55,6 +53,9 @@ func (j *JSONPb) marshalTo(w io.Writer, v interface{}) error { // i.e. primitive types, enums; pointers to primitives or enums; maps from // integer/string types to primitives/enums/pointers to messages. func (j *JSONPb) marshalNonProtoField(v interface{}) ([]byte, error) { + if v == nil { + return []byte("null"), nil + } rv := reflect.ValueOf(v) for rv.Kind() == reflect.Ptr { if rv.IsNil() { diff --git a/runtime/marshal_jsonpb_test.go b/runtime/marshal_jsonpb_test.go index 679283b4511..0c3d01bfeba 100644 --- a/runtime/marshal_jsonpb_test.go +++ b/runtime/marshal_jsonpb_test.go @@ -121,25 +121,23 @@ func TestJSONPbMarshal(t *testing.T) { func TestJSONPbMarshalFields(t *testing.T) { var m runtime.JSONPb - for _, spec := range []struct { - val interface{} - want string - }{} { - buf, err := m.Marshal(spec.val) + m.EnumsAsInts = true // builtin fixtures include an enum, expected to be marshaled as int + for _, spec := range builtinFieldFixtures { + buf, err := m.Marshal(spec.data) if err != nil { - t.Errorf("m.Marshal(%#v) failed with %v; want success", spec.val, err) + t.Errorf("m.Marshal(%#v) failed with %v; want success", spec.data, err) } - if got, want := string(buf), spec.want; got != want { - t.Errorf("m.Marshal(%#v) = %q; want %q", spec.val, got, want) + if got, want := string(buf), spec.json; got != want { + t.Errorf("m.Marshal(%#v) = %q; want %q", spec.data, got, want) } } - m.EnumsAsInts = true + m.EnumsAsInts = false buf, err := m.Marshal(examplepb.NumericEnum_ONE) if err != nil { t.Errorf("m.Marshal(%#v) failed with %v; want success", examplepb.NumericEnum_ONE, err) } - if got, want := string(buf), "1"; got != want { + if got, want := string(buf), `"ONE"`; got != want { t.Errorf("m.Marshal(%#v) = %q; want %q", examplepb.NumericEnum_ONE, got, want) } }