From 1e6953ccdfc764a18c26481585d3f63d774abfe2 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 5 Jun 2020 10:20:41 -0400 Subject: [PATCH] Fix proto3 JSON (#6345) * Fix jsonpb * linting * cleanup Co-authored-by: Alexander Bezobchuk --- codec/json.go | 12 +++ codec/types/any.go | 2 +- codec/types/{amino_compat.go => compat.go} | 85 +++++++++++++------ .../{amino_compat_test.go => compat_test.go} | 0 codec/types/types_test.go | 41 +++++++++ 5 files changed, 111 insertions(+), 29 deletions(-) rename codec/types/{amino_compat.go => compat.go} (69%) rename codec/types/{amino_compat_test.go => compat_test.go} (100%) diff --git a/codec/json.go b/codec/json.go index 0bb44df31f..c1449cc739 100644 --- a/codec/json.go +++ b/codec/json.go @@ -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" ) @@ -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 { @@ -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 { diff --git a/codec/types/any.go b/codec/types/any.go index 714c0f78f2..abdd78dd86 100644 --- a/codec/types/any.go +++ b/codec/types/any.go @@ -49,7 +49,7 @@ type Any struct { cachedValue interface{} - aminoCompat *aminoCompat + compat *anyCompat } // NewAnyWithValue constructs a new Any packed with the value provided or diff --git a/codec/types/amino_compat.go b/codec/types/compat.go similarity index 69% rename from codec/types/amino_compat.go rename to codec/types/compat.go index 6cb1223510..c9c24cdcf0 100644 --- a/codec/types/amino_compat.go +++ b/codec/types/compat.go @@ -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, } @@ -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 } @@ -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 } @@ -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 } @@ -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 { @@ -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 } @@ -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 +} diff --git a/codec/types/amino_compat_test.go b/codec/types/compat_test.go similarity index 100% rename from codec/types/amino_compat_test.go rename to codec/types/compat_test.go diff --git a/codec/types/types_test.go b/codec/types/types_test.go index d55dd38a6a..3a49d937b2 100644 --- a/codec/types/types_test.go +++ b/codec/types/types_test.go @@ -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" @@ -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()) +}