Skip to content

Commit 9d43dd2

Browse files
committed
Support non-message fields in JSONPb marshaler
It allows us to specify non-message fields in google.apis.HttpRule.body while using JSONPb marshaler
1 parent 1354711 commit 9d43dd2

File tree

2 files changed

+388
-42
lines changed

2 files changed

+388
-42
lines changed

runtime/marshal_jsonpb.go

Lines changed: 123 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ package runtime
33
import (
44
"bytes"
55
"encoding/json"
6-
"errors"
6+
"fmt"
77
"io"
8+
"reflect"
89

910
"github.com/golang/protobuf/jsonpb"
1011
"github.com/golang/protobuf/proto"
@@ -39,79 +40,159 @@ func (*JSONPb) ContentType() string {
3940
// Currently it can marshal only proto.Message.
4041
// TODO(yugui) Support fields of primitive types in a message.
4142
func (j *JSONPb) Marshal(v interface{}) ([]byte, error) {
43+
if _, ok := v.(proto.Message); !ok {
44+
return j.marshalNonProtoField(v)
45+
}
46+
47+
var buf bytes.Buffer
48+
if err := j.marshalTo(&buf, v); err != nil {
49+
return nil, err
50+
}
51+
return buf.Bytes(), nil
52+
}
53+
54+
func (j *JSONPb) marshalTo(w io.Writer, v interface{}) error {
55+
p, ok := v.(proto.Message)
56+
if !ok {
57+
buf, err := j.marshalNonProtoField(v)
58+
if err != nil {
59+
return err
60+
}
61+
_, err = w.Write(buf)
62+
return err
63+
}
4264
m := &jsonpb.Marshaler{
4365
EnumsAsInts: j.EnumsAsInts,
4466
EmitDefaults: j.EmitDefaults,
4567
Indent: j.Indent,
4668
OrigName: j.OrigName,
4769
}
48-
p, ok := v.(proto.Message)
49-
if !ok {
50-
return nil, errors.New("interface is not proto.Message")
51-
}
70+
return m.Marshal(w, p)
71+
}
5272

53-
var buf bytes.Buffer
54-
if err := m.Marshal(&buf, p); err != nil {
55-
return nil, err
73+
// marshalNonProto marshals a non-message field of a protobuf message.
74+
// This function does not correctly marshals arbitary data structure into JSON,
75+
// but it is only capable of marshaling non-message field values of protobuf,
76+
// i.e. primitive types, enums; pointers to primitives or enums; maps from
77+
// integer/string types to primitives/enums/pointers to messages.
78+
func (j *JSONPb) marshalNonProtoField(v interface{}) ([]byte, error) {
79+
rv := reflect.ValueOf(v)
80+
for rv.Kind() == reflect.Ptr {
81+
if rv.IsNil() {
82+
return []byte("null"), nil
83+
}
84+
rv = rv.Elem()
5685
}
57-
return buf.Bytes(), nil
5886

87+
if rv.Kind() == reflect.Map {
88+
m := make(map[string]*json.RawMessage)
89+
for _, k := range rv.MapKeys() {
90+
buf, err := j.Marshal(rv.MapIndex(k).Interface())
91+
if err != nil {
92+
return nil, err
93+
}
94+
m[fmt.Sprintf("%v", k.Interface())] = (*json.RawMessage)(&buf)
95+
}
96+
if j.Indent != "" {
97+
return json.MarshalIndent(m, "", j.Indent)
98+
}
99+
return json.Marshal(m)
100+
}
101+
if enum, ok := rv.Interface().(protoEnum); ok && !j.EnumsAsInts {
102+
return json.Marshal(enum.String())
103+
}
104+
return json.Marshal(rv.Interface())
59105
}
60106

61107
// Unmarshal unmarshals JSON "data" into "v"
62108
// Currently it can marshal only proto.Message.
63109
// TODO(yugui) Support fields of primitive types in a message.
64110
func (j *JSONPb) Unmarshal(data []byte, v interface{}) error {
65-
r := bytes.NewReader(data)
66-
p, ok := v.(proto.Message)
67-
if !ok {
68-
return errors.New("interface is not proto.Message")
69-
}
70-
return jsonpb.Unmarshal(r, p)
111+
return unmarshalJSONPb(data, v)
71112
}
72113

73114
// NewDecoder returns a Decoder which reads JSON stream from "r".
74115
func (j *JSONPb) NewDecoder(r io.Reader) Decoder {
75-
return &jsonPbDecoder{decoder: json.NewDecoder(r)}
116+
d := json.NewDecoder(r)
117+
return DecoderFunc(func(v interface{}) error { return decodeJSONPb(d, v) })
76118
}
77119

78120
// NewEncoder returns an Encoder which writes JSON stream into "w".
79121
func (j *JSONPb) NewEncoder(w io.Writer) Encoder {
80-
m := &jsonpb.Marshaler{
81-
EnumsAsInts: j.EnumsAsInts,
82-
EmitDefaults: j.EmitDefaults,
83-
Indent: j.Indent,
84-
OrigName: j.OrigName,
85-
}
86-
87-
return &jsonPbEncoder{
88-
marshal: m,
89-
writer: w,
90-
}
122+
return EncoderFunc(func(v interface{}) error { return j.marshalTo(w, v) })
91123
}
92124

93-
type jsonPbDecoder struct {
94-
decoder *json.Decoder
125+
func unmarshalJSONPb(data []byte, v interface{}) error {
126+
d := json.NewDecoder(bytes.NewReader(data))
127+
return decodeJSONPb(d, v)
95128
}
96129

97-
func (j *jsonPbDecoder) Decode(v interface{}) error {
130+
func decodeJSONPb(d *json.Decoder, v interface{}) error {
98131
p, ok := v.(proto.Message)
99132
if !ok {
100-
return errors.New("interface is not proto.Message")
133+
return decodeNonProtoField(d, v)
101134
}
102-
103-
return jsonpb.UnmarshalNext(j.decoder, p)
135+
return jsonpb.UnmarshalNext(d, p)
104136
}
105137

106-
type jsonPbEncoder struct {
107-
marshal *jsonpb.Marshaler
108-
writer io.Writer
138+
func decodeNonProtoField(d *json.Decoder, v interface{}) error {
139+
rv := reflect.ValueOf(v)
140+
if rv.Kind() != reflect.Ptr {
141+
return fmt.Errorf("%T is not a pointer", v)
142+
}
143+
for rv.Kind() == reflect.Ptr {
144+
if rv.IsNil() {
145+
rv.Set(reflect.New(rv.Type().Elem()))
146+
}
147+
rv = rv.Elem()
148+
}
149+
if rv.Kind() == reflect.Map {
150+
if rv.IsNil() {
151+
rv.Set(reflect.MakeMap(rv.Type()))
152+
}
153+
conv, ok := convFromType[rv.Type().Key().Kind()]
154+
if !ok {
155+
return fmt.Errorf("unsupported type of map field key: %v", rv.Type().Key())
156+
}
157+
158+
m := make(map[string]*json.RawMessage)
159+
if err := d.Decode(&m); err != nil {
160+
return err
161+
}
162+
for k, v := range m {
163+
result := conv.Call([]reflect.Value{reflect.ValueOf(k)})
164+
if err := result[1].Interface(); err != nil {
165+
return err.(error)
166+
}
167+
bk := result[0]
168+
bv := reflect.New(rv.Type().Elem())
169+
if err := unmarshalJSONPb([]byte(*v), bv.Interface()); err != nil {
170+
return err
171+
}
172+
rv.SetMapIndex(bk, bv.Elem())
173+
}
174+
return nil
175+
}
176+
if _, ok := rv.Interface().(protoEnum); ok {
177+
var repr interface{}
178+
if err := d.Decode(&repr); err != nil {
179+
return err
180+
}
181+
switch repr.(type) {
182+
case string:
183+
// TODO(yugui) Should use proto.StructProperties?
184+
return fmt.Errorf("unmarshaling of symbolic enum %q not supported: %T", repr, rv.Interface())
185+
case float64:
186+
rv.Set(reflect.ValueOf(int32(repr.(float64))).Convert(rv.Type()))
187+
return nil
188+
default:
189+
return fmt.Errorf("cannot assign %#v into Go type %T", repr, rv.Interface())
190+
}
191+
}
192+
return d.Decode(v)
109193
}
110194

111-
func (j *jsonPbEncoder) Encode(v interface{}) error {
112-
p, ok := v.(proto.Message)
113-
if !ok {
114-
return errors.New("interface is not proto.Message")
115-
}
116-
return j.marshal.Marshal(j.writer, p)
195+
type protoEnum interface {
196+
fmt.Stringer
197+
EnumDescriptor() ([]byte, []int)
117198
}

0 commit comments

Comments
 (0)