Fix proto3 JSON (#6345)

* Fix jsonpb

* linting

* cleanup

Co-authored-by: Alexander Bezobchuk <alexanderbez@users.noreply.github.com>
This commit is contained in:
Aaron Craelius 2020-06-05 10:20:41 -04:00 committed by GitHub
parent e248693ac8
commit 1e6953ccdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 111 additions and 29 deletions

View File

@ -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 {

View File

@ -49,7 +49,7 @@ type Any struct {
cachedValue interface{}
aminoCompat *aminoCompat
compat *anyCompat
}
// NewAnyWithValue constructs a new Any packed with the value provided or

View File

@ -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
}

View File

@ -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())
}