Skip to content

Commit

Permalink
Support custom JSON (de)serialization in jsonpb
Browse files Browse the repository at this point in the history
Introduce jsonpb JSONPBMarshaler and JSONPBUnmarshaler interfaces for custom JSON serialization and deserialization of dynamic messages.  jsonpb will delegate to these interface methods passing in the default Marhaler/Unmarshaler in order to preserve the options as well as reuse these for regular protobuf values within the dynamic message.
  • Loading branch information
jhump authored and cybrcodr committed May 22, 2017
1 parent fec3b39 commit a4e8f93
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 0 deletions.
29 changes: 29 additions & 0 deletions jsonpb/jsonpb.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,22 @@ type Marshaler struct {
OrigName bool
}

// JSONPBMarshaler is implemented by protobuf messages that customize the
// way they are marshaled to JSON. Messages that implement this should
// also implement JSONPBUnmarshaler so that the custom format can be
// parsed.
type JSONPBMarshaler interface {
MarshalJSONPB(*Marshaler) ([]byte, error)
}

// JSONPBUnmarshaler is implemented by protobuf messages that customize
// the way they are unmarshaled from JSON. Messages that implement this
// should also implement JSONPBMarshaler so that the custom format can be
// produced.
type JSONPBUnmarshaler interface {
UnmarshalJSONPB(*Unmarshaler, []byte) error
}

// Marshal marshals a protocol buffer into JSON.
func (m *Marshaler) Marshal(out io.Writer, pb proto.Message) error {
writer := &errWriter{writer: out}
Expand Down Expand Up @@ -102,6 +118,15 @@ type wkt interface {

// marshalObject writes a struct to the Writer.
func (m *Marshaler) marshalObject(out *errWriter, v proto.Message, indent, typeURL string) error {
if jsm, ok := v.(JSONPBMarshaler); ok {
b, err := jsm.MarshalJSONPB(m)
if err != nil {
return err
}
out.write(string(b))
return out.err
}

s := reflect.ValueOf(v).Elem()

// Handle well-known types.
Expand Down Expand Up @@ -571,6 +596,10 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
return u.unmarshalValue(target.Elem(), inputValue, prop)
}

if jsu, ok := target.Addr().Interface().(JSONPBUnmarshaler); ok {
return jsu.UnmarshalJSONPB(u, []byte(inputValue))
}

// Handle well-known types.
type wkt interface {
XXX_WellKnownType() string
Expand Down
50 changes: 50 additions & 0 deletions jsonpb/jsonpb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,18 @@ func TestMarshaling(t *testing.T) {
}
}

func TestMarshalingWithJSONPBMarshaler(t *testing.T) {
rawJson := `{ "foo": "bar", "baz": [0, 1, 2, 3] }`
msg := dynamicMessage{rawJson: rawJson}
str, err := new(Marshaler).MarshalToString(&msg)
if err != nil {
t.Errorf("an unexpected error occurred when marshalling JSONPBMarshaler: %v", err)
}
if str != rawJson {
t.Errorf("marshalling JSON produced incorrect output: got %s, wanted %s", str, rawJson)
}
}

var unmarshalingTests = []struct {
desc string
unmarshaler Unmarshaler
Expand Down Expand Up @@ -635,3 +647,41 @@ func TestUnmarshalingBadInput(t *testing.T) {
}
}
}

func TestUnmarshalWithJSONPBUnmarshaler(t *testing.T) {
rawJson := `{ "foo": "bar", "baz": [0, 1, 2, 3] }`
var msg dynamicMessage
err := Unmarshal(strings.NewReader(rawJson), &msg)
if err != nil {
t.Errorf("an unexpected error occurred when parsing into JSONPBUnmarshaler: %v", err)
}
if msg.rawJson != rawJson {
t.Errorf("message contents not set correctly after unmarshalling JSON: got %s, wanted %s", msg.rawJson, rawJson)
}
}

// dynamicMessage implements protobuf.Message but is not a normal generated message type.
// It provides implementations of JSONPBMarshaler and JSONPBUnmarshaler for JSON support.
type dynamicMessage struct {
rawJson string
}

func (m *dynamicMessage) Reset() {
m.rawJson = "{}"
}

func (m *dynamicMessage) String() string {
return m.rawJson
}

func (m *dynamicMessage) ProtoMessage() {
}

func (m *dynamicMessage) MarshalJSONPB(jm *Marshaler) ([]byte, error) {
return []byte(m.rawJson), nil
}

func (m *dynamicMessage) UnmarshalJSONPB(jum *Unmarshaler, json []byte) error {
m.rawJson = string(json)
return nil
}

0 comments on commit a4e8f93

Please sign in to comment.