Skip to content

Commit

Permalink
Fix proto3 JSON (cosmos#6345)
Browse files Browse the repository at this point in the history
* Fix jsonpb

* linting

* cleanup

Co-authored-by: Alexander Bezobchuk <alexanderbez@users.noreply.github.com>
  • Loading branch information
aaronc and alexanderbez authored Jun 5, 2020
1 parent e248693 commit 1e6953c
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 29 deletions.
12 changes: 12 additions & 0 deletions codec/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"bytes"
"encoding/json"

"github.com/cosmos/cosmos-sdk/codec/types"

"github.com/gogo/protobuf/jsonpb"
"github.com/gogo/protobuf/proto"
)
Expand All @@ -25,6 +27,11 @@ func MarshalIndentFromJSON(bz []byte) ([]byte, error) {
// bytes of a message.
func ProtoMarshalJSON(msg proto.Message) ([]byte, error) {
jm := &jsonpb.Marshaler{EmitDefaults: false, OrigName: false}
err := types.UnpackInterfaces(msg, types.ProtoJSONPacker{JSONPBMarshaler: jm})
if err != nil {
return nil, err
}

buf := new(bytes.Buffer)

if err := jm.Marshal(buf, msg); err != nil {
Expand All @@ -38,6 +45,11 @@ func ProtoMarshalJSON(msg proto.Message) ([]byte, error) {
// JSON encoded bytes of a message.
func ProtoMarshalJSONIndent(msg proto.Message) ([]byte, error) {
jm := &jsonpb.Marshaler{EmitDefaults: false, OrigName: false, Indent: " "}
err := types.UnpackInterfaces(msg, types.ProtoJSONPacker{JSONPBMarshaler: jm})
if err != nil {
return nil, err
}

buf := new(bytes.Buffer)

if err := jm.Marshal(buf, msg); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion codec/types/any.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type Any struct {

cachedValue interface{}

aminoCompat *aminoCompat
compat *anyCompat
}

// NewAnyWithValue constructs a new Any packed with the value provided or
Expand Down
85 changes: 57 additions & 28 deletions codec/types/amino_compat.go → codec/types/compat.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,61 @@ import (
"reflect"
"runtime/debug"

"github.com/gogo/protobuf/jsonpb"
"github.com/gogo/protobuf/proto"

amino "github.com/tendermint/go-amino"
)

type aminoCompat struct {
bz []byte
jsonBz []byte
err error
type anyCompat struct {
aminoBz []byte
jsonBz []byte
err error
}

var Debug = false
var Debug = true

func aminoCompatError(errType string, x interface{}) error {
func anyCompatError(errType string, x interface{}) error {
if Debug {
debug.PrintStack()
}
return fmt.Errorf(
"amino %s Any marshaling error for %+v, this is likely because "+
"%s marshaling error for %+v, this is likely because "+
"amino is being used directly (instead of codec.Codec which is preferred) "+
"or UnpackInterfacesMessage is not defined for some type which contains "+
"a protobuf Any either directly or via one of its members. To see a "+
"stacktrace of where the error is coming from, set the var Debug = true "+
"in codec/types/amino_compat.go",
"in codec/types/compat.go",
errType, x,
)
}

func (any Any) MarshalAmino() ([]byte, error) {
ac := any.aminoCompat
ac := any.compat
if ac == nil {
return nil, aminoCompatError("binary unmarshal", any)
return nil, anyCompatError("amino binary unmarshal", any)
}
return ac.bz, ac.err
return ac.aminoBz, ac.err
}

func (any *Any) UnmarshalAmino(bz []byte) error {
any.aminoCompat = &aminoCompat{
bz: bz,
err: nil,
any.compat = &anyCompat{
aminoBz: bz,
err: nil,
}
return nil
}

func (any Any) MarshalJSON() ([]byte, error) {
ac := any.aminoCompat
ac := any.compat
if ac == nil {
return nil, aminoCompatError("JSON marshal", any)
return nil, anyCompatError("JSON marshal", any)
}
return ac.jsonBz, ac.err
}

func (any *Any) UnmarshalJSON(bz []byte) error {
any.aminoCompat = &aminoCompat{
any.compat = &anyCompat{
jsonBz: bz,
err: nil,
}
Expand All @@ -74,11 +75,11 @@ type AminoUnpacker struct {
var _ AnyUnpacker = AminoUnpacker{}

func (a AminoUnpacker) UnpackAny(any *Any, iface interface{}) error {
ac := any.aminoCompat
ac := any.compat
if ac == nil {
return aminoCompatError("binary unmarshal", reflect.TypeOf(iface))
return anyCompatError("amino binary unmarshal", reflect.TypeOf(iface))
}
err := a.Cdc.UnmarshalBinaryBare(ac.bz, iface)
err := a.Cdc.UnmarshalBinaryBare(ac.aminoBz, iface)
if err != nil {
return err
}
Expand All @@ -98,7 +99,7 @@ func (a AminoUnpacker) UnpackAny(any *Any, iface interface{}) error {

// this is necessary for tests that use reflect.DeepEqual and compare
// proto vs amino marshaled values
any.aminoCompat = nil
any.compat = nil

return nil
}
Expand All @@ -117,9 +118,9 @@ func (a AminoPacker) UnpackAny(any *Any, _ interface{}) error {
return err
}
bz, err := a.Cdc.MarshalBinaryBare(any.cachedValue)
any.aminoCompat = &aminoCompat{
bz: bz,
err: err,
any.compat = &anyCompat{
aminoBz: bz,
err: err,
}
return err
}
Expand All @@ -133,9 +134,9 @@ type AminoJSONUnpacker struct {
var _ AnyUnpacker = AminoJSONUnpacker{}

func (a AminoJSONUnpacker) UnpackAny(any *Any, iface interface{}) error {
ac := any.aminoCompat
ac := any.compat
if ac == nil {
return aminoCompatError("JSON unmarshal", reflect.TypeOf(iface))
return anyCompatError("JSON unmarshal", reflect.TypeOf(iface))
}
err := a.Cdc.UnmarshalJSON(ac.jsonBz, iface)
if err != nil {
Expand All @@ -157,7 +158,7 @@ func (a AminoJSONUnpacker) UnpackAny(any *Any, iface interface{}) error {

// this is necessary for tests that use reflect.DeepEqual and compare
// proto vs amino marshaled values
any.aminoCompat = nil
any.compat = nil

return nil
}
Expand All @@ -176,9 +177,37 @@ func (a AminoJSONPacker) UnpackAny(any *Any, _ interface{}) error {
return err
}
bz, err := a.Cdc.MarshalJSON(any.cachedValue)
any.aminoCompat = &aminoCompat{
any.compat = &anyCompat{
jsonBz: bz,
err: err,
}
return err
}

// ProtoJSONPacker is an AnyUnpacker provided for compatibility with jsonpb
type ProtoJSONPacker struct {
JSONPBMarshaler *jsonpb.Marshaler
}

var _ AnyUnpacker = ProtoJSONPacker{}

func (a ProtoJSONPacker) UnpackAny(any *Any, _ interface{}) error {
if any == nil {
return nil
}

if any.cachedValue != nil {
err := UnpackInterfaces(any.cachedValue, a)
if err != nil {
return err
}
}

bz, err := a.JSONPBMarshaler.MarshalToString(any)
any.compat = &anyCompat{
jsonBz: []byte(bz),
err: err,
}

return err
}
File renamed without changes.
41 changes: 41 additions & 0 deletions codec/types/types_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package types_test

import (
"strings"
"testing"

"github.com/gogo/protobuf/jsonpb"

"github.com/cosmos/cosmos-sdk/codec/types"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -131,3 +134,41 @@ func TestNested(t *testing.T) {

require.Equal(t, spot, hhha2.TheHasHasAnimal().TheHasAnimal().TheAnimal())
}

func TestAny_ProtoJSON(t *testing.T) {
spot := &testdata.Dog{Name: "Spot"}
any, err := types.NewAnyWithValue(spot)
require.NoError(t, err)

jm := &jsonpb.Marshaler{}
json, err := jm.MarshalToString(any)
require.NoError(t, err)
require.Equal(t, "{\"@type\":\"/cosmos_sdk.codec.v1.Dog\",\"name\":\"Spot\"}", json)

registry := NewTestInterfaceRegistry()
jum := &jsonpb.Unmarshaler{}
var any2 types.Any
err = jum.Unmarshal(strings.NewReader(json), &any2)
require.NoError(t, err)
var animal testdata.Animal
err = registry.UnpackAny(&any2, &animal)
require.NoError(t, err)
require.Equal(t, spot, animal)

ha := &testdata.HasAnimal{
Animal: any,
}
err = ha.UnpackInterfaces(types.ProtoJSONPacker{JSONPBMarshaler: jm})
require.NoError(t, err)
json, err = jm.MarshalToString(ha)
require.NoError(t, err)
require.Equal(t, "{\"animal\":{\"@type\":\"/cosmos_sdk.codec.v1.Dog\",\"name\":\"Spot\"}}", json)

require.NoError(t, err)
var ha2 testdata.HasAnimal
err = jum.Unmarshal(strings.NewReader(json), &ha2)
require.NoError(t, err)
err = ha2.UnpackInterfaces(registry)
require.NoError(t, err)
require.Equal(t, spot, ha2.Animal.GetCachedValue())
}

0 comments on commit 1e6953c

Please sign in to comment.