Last active
February 19, 2018 23:56
-
-
Save abursavich/d8cae1b0dee39cb17e05095e088d113f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Package gogomarshal contains marshaling code extracted from | |
// github.com/grpc-ecosystem/grpc-gateway/runtime and altered | |
// to depend on gogo versions of the proto and jsonpb packages. | |
package gogomarshal | |
import ( | |
"bytes" | |
"encoding/json" | |
"fmt" | |
"io" | |
"reflect" | |
"github.com/gogo/protobuf/jsonpb" | |
"github.com/gogo/protobuf/proto" | |
"github.com/grpc-ecosystem/grpc-gateway/runtime" | |
) | |
// JSONPb is a Marshaler which marshals/unmarshals into/from JSON | |
// with the "github.com/gogo/protobuf/jsonpb". | |
// It supports fully functionality of protobuf unlike JSONBuiltin. | |
type JSONPb jsonpb.Marshaler | |
// 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) { | |
if _, ok := v.(proto.Message); !ok { | |
return j.marshalNonProtoField(v) | |
} | |
var buf bytes.Buffer | |
if err := j.marshalTo(&buf, v); err != nil { | |
return nil, err | |
} | |
return buf.Bytes(), nil | |
} | |
func (j *JSONPb) marshalTo(w io.Writer, v interface{}) error { | |
p, ok := v.(proto.Message) | |
if !ok { | |
buf, err := j.marshalNonProtoField(v) | |
if err != nil { | |
return err | |
} | |
_, err = w.Write(buf) | |
return err | |
} | |
return (*jsonpb.Marshaler)(j).Marshal(w, p) | |
} | |
// marshalNonProto marshals a non-message field of a protobuf message. | |
// This function does not correctly marshals arbitary data structure into JSON, | |
// but it is only capable of marshaling non-message field values of protobuf, | |
// 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) { | |
rv := reflect.ValueOf(v) | |
for rv.Kind() == reflect.Ptr { | |
if rv.IsNil() { | |
return []byte("null"), nil | |
} | |
rv = rv.Elem() | |
} | |
if rv.Kind() == reflect.Map { | |
m := make(map[string]*json.RawMessage) | |
for _, k := range rv.MapKeys() { | |
buf, err := j.Marshal(rv.MapIndex(k).Interface()) | |
if err != nil { | |
return nil, err | |
} | |
m[fmt.Sprintf("%v", k.Interface())] = (*json.RawMessage)(&buf) | |
} | |
if j.Indent != "" { | |
return json.MarshalIndent(m, "", j.Indent) | |
} | |
return json.Marshal(m) | |
} | |
if enum, ok := rv.Interface().(protoEnum); ok && !j.EnumsAsInts { | |
return json.Marshal(enum.String()) | |
} | |
return json.Marshal(rv.Interface()) | |
} | |
// 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 { | |
return unmarshalJSONPb(data, v) | |
} | |
// NewDecoder returns a runtime.Decoder which reads JSON stream from "r". | |
func (j *JSONPb) NewDecoder(r io.Reader) runtime.Decoder { | |
d := json.NewDecoder(r) | |
return runtime.DecoderFunc(func(v interface{}) error { return decodeJSONPb(d, v) }) | |
} | |
// NewEncoder returns an Encoder which writes JSON stream into "w". | |
func (j *JSONPb) NewEncoder(w io.Writer) runtime.Encoder { | |
return runtime.EncoderFunc(func(v interface{}) error { return j.marshalTo(w, v) }) | |
} | |
func unmarshalJSONPb(data []byte, v interface{}) error { | |
d := json.NewDecoder(bytes.NewReader(data)) | |
return decodeJSONPb(d, v) | |
} | |
func decodeJSONPb(d *json.Decoder, v interface{}) error { | |
p, ok := v.(proto.Message) | |
if !ok { | |
return decodeNonProtoField(d, v) | |
} | |
unmarshaler := &jsonpb.Unmarshaler{AllowUnknownFields: true} | |
return unmarshaler.UnmarshalNext(d, p) | |
} | |
func decodeNonProtoField(d *json.Decoder, v interface{}) error { | |
rv := reflect.ValueOf(v) | |
if rv.Kind() != reflect.Ptr { | |
return fmt.Errorf("%T is not a pointer", v) | |
} | |
for rv.Kind() == reflect.Ptr { | |
if rv.IsNil() { | |
rv.Set(reflect.New(rv.Type().Elem())) | |
} | |
if rv.Type().ConvertibleTo(typeProtoMessage) { | |
unmarshaler := &jsonpb.Unmarshaler{AllowUnknownFields: true} | |
return unmarshaler.UnmarshalNext(d, rv.Interface().(proto.Message)) | |
} | |
rv = rv.Elem() | |
} | |
if rv.Kind() == reflect.Map { | |
if rv.IsNil() { | |
rv.Set(reflect.MakeMap(rv.Type())) | |
} | |
conv, ok := convFromType[rv.Type().Key().Kind()] | |
if !ok { | |
return fmt.Errorf("unsupported type of map field key: %v", rv.Type().Key()) | |
} | |
m := make(map[string]*json.RawMessage) | |
if err := d.Decode(&m); err != nil { | |
return err | |
} | |
for k, v := range m { | |
result := conv.Call([]reflect.Value{reflect.ValueOf(k)}) | |
if err := result[1].Interface(); err != nil { | |
return err.(error) | |
} | |
bk := result[0] | |
bv := reflect.New(rv.Type().Elem()) | |
if err := unmarshalJSONPb([]byte(*v), bv.Interface()); err != nil { | |
return err | |
} | |
rv.SetMapIndex(bk, bv.Elem()) | |
} | |
return nil | |
} | |
if _, ok := rv.Interface().(protoEnum); ok { | |
var repr interface{} | |
if err := d.Decode(&repr); err != nil { | |
return err | |
} | |
switch repr.(type) { | |
case string: | |
// TODO(yugui) Should use proto.StructProperties? | |
return fmt.Errorf("unmarshaling of symbolic enum %q not supported: %T", repr, rv.Interface()) | |
case float64: | |
rv.Set(reflect.ValueOf(int32(repr.(float64))).Convert(rv.Type())) | |
return nil | |
default: | |
return fmt.Errorf("cannot assign %#v into Go type %T", repr, rv.Interface()) | |
} | |
} | |
return d.Decode(v) | |
} | |
type protoEnum interface { | |
fmt.Stringer | |
EnumDescriptor() ([]byte, []int) | |
} | |
var typeProtoMessage = reflect.TypeOf((*proto.Message)(nil)).Elem() | |
// Delimiter for newline encoded JSON streams. | |
func (j *JSONPb) Delimiter() []byte { | |
return []byte("\n") | |
} | |
var convFromType = map[reflect.Kind]reflect.Value{ | |
reflect.String: reflect.ValueOf(runtime.String), | |
reflect.Bool: reflect.ValueOf(runtime.Bool), | |
reflect.Float64: reflect.ValueOf(runtime.Float64), | |
reflect.Float32: reflect.ValueOf(runtime.Float32), | |
reflect.Int64: reflect.ValueOf(runtime.Int64), | |
reflect.Int32: reflect.ValueOf(runtime.Int32), | |
reflect.Uint64: reflect.ValueOf(runtime.Uint64), | |
reflect.Uint32: reflect.ValueOf(runtime.Uint32), | |
reflect.Slice: reflect.ValueOf(runtime.Bytes), | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment