blob: e8db20b040e5211854a15b4ca4d7873400ee5e5c [file] [log] [blame]
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package protojson_test
import (
"bytes"
"math"
"testing"
"github.com/google/go-cmp/cmp"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/internal/detrand"
"google.golang.org/protobuf/internal/flags"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/testing/protopack"
pb2 "google.golang.org/protobuf/internal/testprotos/textpb2"
pb3 "google.golang.org/protobuf/internal/testprotos/textpb3"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/fieldmaskpb"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
// Disable detrand to enable direct comparisons on outputs.
func init() { detrand.Disable() }
func TestMarshal(t *testing.T) {
tests := []struct {
desc string
mo protojson.MarshalOptions
input proto.Message
want string
wantErr bool // TODO: Verify error message substring.
skip bool
}{{
desc: "proto2 optional scalars not set",
input: &pb2.Scalars{},
want: "{}",
}, {
desc: "proto3 scalars not set",
input: &pb3.Scalars{},
want: "{}",
}, {
desc: "proto3 optional not set",
input: &pb3.Proto3Optional{},
want: "{}",
}, {
desc: "proto2 optional scalars set to zero values",
input: &pb2.Scalars{
OptBool: proto.Bool(false),
OptInt32: proto.Int32(0),
OptInt64: proto.Int64(0),
OptUint32: proto.Uint32(0),
OptUint64: proto.Uint64(0),
OptSint32: proto.Int32(0),
OptSint64: proto.Int64(0),
OptFixed32: proto.Uint32(0),
OptFixed64: proto.Uint64(0),
OptSfixed32: proto.Int32(0),
OptSfixed64: proto.Int64(0),
OptFloat: proto.Float32(0),
OptDouble: proto.Float64(0),
OptBytes: []byte{},
OptString: proto.String(""),
},
want: `{
"optBool": false,
"optInt32": 0,
"optInt64": "0",
"optUint32": 0,
"optUint64": "0",
"optSint32": 0,
"optSint64": "0",
"optFixed32": 0,
"optFixed64": "0",
"optSfixed32": 0,
"optSfixed64": "0",
"optFloat": 0,
"optDouble": 0,
"optBytes": "",
"optString": ""
}`,
}, {
desc: "proto3 optional set to zero values",
input: &pb3.Proto3Optional{
OptBool: proto.Bool(false),
OptInt32: proto.Int32(0),
OptInt64: proto.Int64(0),
OptUint32: proto.Uint32(0),
OptUint64: proto.Uint64(0),
OptFloat: proto.Float32(0),
OptDouble: proto.Float64(0),
OptString: proto.String(""),
OptBytes: []byte{},
OptEnum: pb3.Enum_ZERO.Enum(),
OptMessage: &pb3.Nested{},
},
want: `{
"optBool": false,
"optInt32": 0,
"optInt64": "0",
"optUint32": 0,
"optUint64": "0",
"optFloat": 0,
"optDouble": 0,
"optString": "",
"optBytes": "",
"optEnum": "ZERO",
"optMessage": {}
}`,
}, {
desc: "proto2 optional scalars set to some values",
input: &pb2.Scalars{
OptBool: proto.Bool(true),
OptInt32: proto.Int32(0xff),
OptInt64: proto.Int64(0xdeadbeef),
OptUint32: proto.Uint32(47),
OptUint64: proto.Uint64(0xdeadbeef),
OptSint32: proto.Int32(-1001),
OptSint64: proto.Int64(-0xffff),
OptFixed64: proto.Uint64(64),
OptSfixed32: proto.Int32(-32),
OptFloat: proto.Float32(1.02),
OptDouble: proto.Float64(1.234),
OptBytes: []byte("谷歌"),
OptString: proto.String("谷歌"),
},
want: `{
"optBool": true,
"optInt32": 255,
"optInt64": "3735928559",
"optUint32": 47,
"optUint64": "3735928559",
"optSint32": -1001,
"optSint64": "-65535",
"optFixed64": "64",
"optSfixed32": -32,
"optFloat": 1.02,
"optDouble": 1.234,
"optBytes": "6LC35q2M",
"optString": "谷歌"
}`,
}, {
desc: "string",
input: &pb3.Scalars{
SString: "谷歌",
},
want: `{
"sString": "谷歌"
}`,
}, {
desc: "string with invalid UTF8",
input: &pb3.Scalars{
SString: "abc\xff",
},
wantErr: true,
}, {
desc: "float nan",
input: &pb3.Scalars{
SFloat: float32(math.NaN()),
},
want: `{
"sFloat": "NaN"
}`,
}, {
desc: "float positive infinity",
input: &pb3.Scalars{
SFloat: float32(math.Inf(1)),
},
want: `{
"sFloat": "Infinity"
}`,
}, {
desc: "float negative infinity",
input: &pb3.Scalars{
SFloat: float32(math.Inf(-1)),
},
want: `{
"sFloat": "-Infinity"
}`,
}, {
desc: "double nan",
input: &pb3.Scalars{
SDouble: math.NaN(),
},
want: `{
"sDouble": "NaN"
}`,
}, {
desc: "double positive infinity",
input: &pb3.Scalars{
SDouble: math.Inf(1),
},
want: `{
"sDouble": "Infinity"
}`,
}, {
desc: "double negative infinity",
input: &pb3.Scalars{
SDouble: math.Inf(-1),
},
want: `{
"sDouble": "-Infinity"
}`,
}, {
desc: "proto2 enum not set",
input: &pb2.Enums{},
want: "{}",
}, {
desc: "proto2 enum set to zero value",
input: &pb2.Enums{
OptEnum: pb2.Enum(0).Enum(),
OptNestedEnum: pb2.Enums_NestedEnum(0).Enum(),
},
want: `{
"optEnum": 0,
"optNestedEnum": 0
}`,
}, {
desc: "proto2 enum",
input: &pb2.Enums{
OptEnum: pb2.Enum_ONE.Enum(),
OptNestedEnum: pb2.Enums_UNO.Enum(),
},
want: `{
"optEnum": "ONE",
"optNestedEnum": "UNO"
}`,
}, {
desc: "proto2 enum set to numeric values",
input: &pb2.Enums{
OptEnum: pb2.Enum(2).Enum(),
OptNestedEnum: pb2.Enums_NestedEnum(2).Enum(),
},
want: `{
"optEnum": "TWO",
"optNestedEnum": "DOS"
}`,
}, {
desc: "proto2 enum set to unnamed numeric values",
input: &pb2.Enums{
OptEnum: pb2.Enum(101).Enum(),
OptNestedEnum: pb2.Enums_NestedEnum(-101).Enum(),
},
want: `{
"optEnum": 101,
"optNestedEnum": -101
}`,
}, {
desc: "proto3 enum not set",
input: &pb3.Enums{},
want: "{}",
}, {
desc: "proto3 enum set to zero value",
input: &pb3.Enums{
SEnum: pb3.Enum_ZERO,
SNestedEnum: pb3.Enums_CERO,
},
want: "{}",
}, {
desc: "proto3 enum",
input: &pb3.Enums{
SEnum: pb3.Enum_ONE,
SNestedEnum: pb3.Enums_UNO,
},
want: `{
"sEnum": "ONE",
"sNestedEnum": "UNO"
}`,
}, {
desc: "proto3 enum set to numeric values",
input: &pb3.Enums{
SEnum: 2,
SNestedEnum: 2,
},
want: `{
"sEnum": "TWO",
"sNestedEnum": "DOS"
}`,
}, {
desc: "proto3 enum set to unnamed numeric values",
input: &pb3.Enums{
SEnum: -47,
SNestedEnum: 47,
},
want: `{
"sEnum": -47,
"sNestedEnum": 47
}`,
}, {
desc: "proto2 nested message not set",
input: &pb2.Nests{},
want: "{}",
}, {
desc: "proto2 nested message set to empty",
input: &pb2.Nests{
OptNested: &pb2.Nested{},
Optgroup: &pb2.Nests_OptGroup{},
},
want: `{
"optNested": {},
"optgroup": {}
}`,
}, {
desc: "proto2 nested messages",
input: &pb2.Nests{
OptNested: &pb2.Nested{
OptString: proto.String("nested message"),
OptNested: &pb2.Nested{
OptString: proto.String("another nested message"),
},
},
},
want: `{
"optNested": {
"optString": "nested message",
"optNested": {
"optString": "another nested message"
}
}
}`,
}, {
desc: "proto2 groups",
input: &pb2.Nests{
Optgroup: &pb2.Nests_OptGroup{
OptString: proto.String("inside a group"),
OptNested: &pb2.Nested{
OptString: proto.String("nested message inside a group"),
},
Optnestedgroup: &pb2.Nests_OptGroup_OptNestedGroup{
OptFixed32: proto.Uint32(47),
},
},
},
want: `{
"optgroup": {
"optString": "inside a group",
"optNested": {
"optString": "nested message inside a group"
},
"optnestedgroup": {
"optFixed32": 47
}
}
}`,
}, {
desc: "proto3 nested message not set",
input: &pb3.Nests{},
want: "{}",
}, {
desc: "proto3 nested message set to empty",
input: &pb3.Nests{
SNested: &pb3.Nested{},
},
want: `{
"sNested": {}
}`,
}, {
desc: "proto3 nested message",
input: &pb3.Nests{
SNested: &pb3.Nested{
SString: "nested message",
SNested: &pb3.Nested{
SString: "another nested message",
},
},
},
want: `{
"sNested": {
"sString": "nested message",
"sNested": {
"sString": "another nested message"
}
}
}`,
}, {
desc: "oneof not set",
input: &pb3.Oneofs{},
want: "{}",
}, {
desc: "oneof set to empty string",
input: &pb3.Oneofs{
Union: &pb3.Oneofs_OneofString{},
},
want: `{
"oneofString": ""
}`,
}, {
desc: "oneof set to string",
input: &pb3.Oneofs{
Union: &pb3.Oneofs_OneofString{
OneofString: "hello",
},
},
want: `{
"oneofString": "hello"
}`,
}, {
desc: "oneof set to enum",
input: &pb3.Oneofs{
Union: &pb3.Oneofs_OneofEnum{
OneofEnum: pb3.Enum_ZERO,
},
},
want: `{
"oneofEnum": "ZERO"
}`,
}, {
desc: "oneof set to empty message",
input: &pb3.Oneofs{
Union: &pb3.Oneofs_OneofNested{
OneofNested: &pb3.Nested{},
},
},
want: `{
"oneofNested": {}
}`,
}, {
desc: "oneof set to message",
input: &pb3.Oneofs{
Union: &pb3.Oneofs_OneofNested{
OneofNested: &pb3.Nested{
SString: "nested message",
},
},
},
want: `{
"oneofNested": {
"sString": "nested message"
}
}`,
}, {
desc: "repeated fields not set",
input: &pb2.Repeats{},
want: "{}",
}, {
desc: "repeated fields set to empty slices",
input: &pb2.Repeats{
RptBool: []bool{},
RptInt32: []int32{},
RptInt64: []int64{},
RptUint32: []uint32{},
RptUint64: []uint64{},
RptFloat: []float32{},
RptDouble: []float64{},
RptBytes: [][]byte{},
},
want: "{}",
}, {
desc: "repeated fields set to some values",
input: &pb2.Repeats{
RptBool: []bool{true, false, true, true},
RptInt32: []int32{1, 6, 0, 0},
RptInt64: []int64{-64, 47},
RptUint32: []uint32{0xff, 0xffff},
RptUint64: []uint64{0xdeadbeef},
RptFloat: []float32{float32(math.NaN()), float32(math.Inf(1)), float32(math.Inf(-1)), 1.034},
RptDouble: []float64{math.NaN(), math.Inf(1), math.Inf(-1), 1.23e-308},
RptString: []string{"hello", "世界"},
RptBytes: [][]byte{
[]byte("hello"),
[]byte("\xe4\xb8\x96\xe7\x95\x8c"),
},
},
want: `{
"rptBool": [
true,
false,
true,
true
],
"rptInt32": [
1,
6,
0,
0
],
"rptInt64": [
"-64",
"47"
],
"rptUint32": [
255,
65535
],
"rptUint64": [
"3735928559"
],
"rptFloat": [
"NaN",
"Infinity",
"-Infinity",
1.034
],
"rptDouble": [
"NaN",
"Infinity",
"-Infinity",
1.23e-308
],
"rptString": [
"hello",
"世界"
],
"rptBytes": [
"aGVsbG8=",
"5LiW55WM"
]
}`,
}, {
desc: "repeated enums",
input: &pb2.Enums{
RptEnum: []pb2.Enum{pb2.Enum_ONE, 2, pb2.Enum_TEN, 42},
RptNestedEnum: []pb2.Enums_NestedEnum{2, 47, 10},
},
want: `{
"rptEnum": [
"ONE",
"TWO",
"TEN",
42
],
"rptNestedEnum": [
"DOS",
47,
"DIEZ"
]
}`,
}, {
desc: "repeated messages set to empty",
input: &pb2.Nests{
RptNested: []*pb2.Nested{},
Rptgroup: []*pb2.Nests_RptGroup{},
},
want: "{}",
}, {
desc: "repeated messages",
input: &pb2.Nests{
RptNested: []*pb2.Nested{
{
OptString: proto.String("repeat nested one"),
},
{
OptString: proto.String("repeat nested two"),
OptNested: &pb2.Nested{
OptString: proto.String("inside repeat nested two"),
},
},
{},
},
},
want: `{
"rptNested": [
{
"optString": "repeat nested one"
},
{
"optString": "repeat nested two",
"optNested": {
"optString": "inside repeat nested two"
}
},
{}
]
}`,
}, {
desc: "repeated messages contains nil value",
input: &pb2.Nests{
RptNested: []*pb2.Nested{nil, {}},
},
want: `{
"rptNested": [
{},
{}
]
}`,
}, {
desc: "repeated groups",
input: &pb2.Nests{
Rptgroup: []*pb2.Nests_RptGroup{
{
RptString: []string{"hello", "world"},
},
{},
nil,
},
},
want: `{
"rptgroup": [
{
"rptString": [
"hello",
"world"
]
},
{},
{}
]
}`,
}, {
desc: "map fields not set",
input: &pb3.Maps{},
want: "{}",
}, {
desc: "map fields set to empty",
input: &pb3.Maps{
Int32ToStr: map[int32]string{},
BoolToUint32: map[bool]uint32{},
Uint64ToEnum: map[uint64]pb3.Enum{},
StrToNested: map[string]*pb3.Nested{},
StrToOneofs: map[string]*pb3.Oneofs{},
},
want: "{}",
}, {
desc: "map fields 1",
input: &pb3.Maps{
BoolToUint32: map[bool]uint32{
true: 42,
false: 101,
},
},
want: `{
"boolToUint32": {
"false": 101,
"true": 42
}
}`,
}, {
desc: "map fields 2",
input: &pb3.Maps{
Int32ToStr: map[int32]string{
-101: "-101",
0xff: "0xff",
0: "zero",
},
},
want: `{
"int32ToStr": {
"-101": "-101",
"0": "zero",
"255": "0xff"
}
}`,
}, {
desc: "map fields 3",
input: &pb3.Maps{
Uint64ToEnum: map[uint64]pb3.Enum{
1: pb3.Enum_ONE,
2: pb3.Enum_TWO,
10: pb3.Enum_TEN,
47: 47,
},
},
want: `{
"uint64ToEnum": {
"1": "ONE",
"2": "TWO",
"10": "TEN",
"47": 47
}
}`,
}, {
desc: "map fields 4",
input: &pb3.Maps{
StrToNested: map[string]*pb3.Nested{
"nested": &pb3.Nested{
SString: "nested in a map",
},
},
},
want: `{
"strToNested": {
"nested": {
"sString": "nested in a map"
}
}
}`,
}, {
desc: "map fields 5",
input: &pb3.Maps{
StrToOneofs: map[string]*pb3.Oneofs{
"string": &pb3.Oneofs{
Union: &pb3.Oneofs_OneofString{
OneofString: "hello",
},
},
"nested": &pb3.Oneofs{
Union: &pb3.Oneofs_OneofNested{
OneofNested: &pb3.Nested{
SString: "nested oneof in map field value",
},
},
},
},
},
want: `{
"strToOneofs": {
"nested": {
"oneofNested": {
"sString": "nested oneof in map field value"
}
},
"string": {
"oneofString": "hello"
}
}
}`,
}, {
desc: "map field contains nil value",
input: &pb3.Maps{
StrToNested: map[string]*pb3.Nested{
"nil": nil,
},
},
want: `{
"strToNested": {
"nil": {}
}
}`,
}, {
desc: "required fields not set",
input: &pb2.Requireds{},
want: `{}`,
wantErr: true,
}, {
desc: "required fields partially set",
input: &pb2.Requireds{
ReqBool: proto.Bool(false),
ReqSfixed64: proto.Int64(0),
ReqDouble: proto.Float64(1.23),
ReqString: proto.String("hello"),
ReqEnum: pb2.Enum_ONE.Enum(),
},
want: `{
"reqBool": false,
"reqSfixed64": "0",
"reqDouble": 1.23,
"reqString": "hello",
"reqEnum": "ONE"
}`,
wantErr: true,
}, {
desc: "required fields not set with AllowPartial",
mo: protojson.MarshalOptions{AllowPartial: true},
input: &pb2.Requireds{
ReqBool: proto.Bool(false),
ReqSfixed64: proto.Int64(0),
ReqDouble: proto.Float64(1.23),
ReqString: proto.String("hello"),
ReqEnum: pb2.Enum_ONE.Enum(),
},
want: `{
"reqBool": false,
"reqSfixed64": "0",
"reqDouble": 1.23,
"reqString": "hello",
"reqEnum": "ONE"
}`,
}, {
desc: "required fields all set",
input: &pb2.Requireds{
ReqBool: proto.Bool(false),
ReqSfixed64: proto.Int64(0),
ReqDouble: proto.Float64(1.23),
ReqString: proto.String("hello"),
ReqEnum: pb2.Enum_ONE.Enum(),
ReqNested: &pb2.Nested{},
},
want: `{
"reqBool": false,
"reqSfixed64": "0",
"reqDouble": 1.23,
"reqString": "hello",
"reqEnum": "ONE",
"reqNested": {}
}`,
}, {
desc: "indirect required field",
input: &pb2.IndirectRequired{
OptNested: &pb2.NestedWithRequired{},
},
want: `{
"optNested": {}
}`,
wantErr: true,
}, {
desc: "indirect required field with AllowPartial",
mo: protojson.MarshalOptions{AllowPartial: true},
input: &pb2.IndirectRequired{
OptNested: &pb2.NestedWithRequired{},
},
want: `{
"optNested": {}
}`,
}, {
desc: "indirect required field in empty repeated",
input: &pb2.IndirectRequired{
RptNested: []*pb2.NestedWithRequired{},
},
want: `{}`,
}, {
desc: "indirect required field in repeated",
input: &pb2.IndirectRequired{
RptNested: []*pb2.NestedWithRequired{
&pb2.NestedWithRequired{},
},
},
want: `{
"rptNested": [
{}
]
}`,
wantErr: true,
}, {
desc: "indirect required field in repeated with AllowPartial",
mo: protojson.MarshalOptions{AllowPartial: true},
input: &pb2.IndirectRequired{
RptNested: []*pb2.NestedWithRequired{
&pb2.NestedWithRequired{},
},
},
want: `{
"rptNested": [
{}
]
}`,
}, {
desc: "indirect required field in empty map",
input: &pb2.IndirectRequired{
StrToNested: map[string]*pb2.NestedWithRequired{},
},
want: "{}",
}, {
desc: "indirect required field in map",
input: &pb2.IndirectRequired{
StrToNested: map[string]*pb2.NestedWithRequired{
"fail": &pb2.NestedWithRequired{},
},
},
want: `{
"strToNested": {
"fail": {}
}
}`,
wantErr: true,
}, {
desc: "indirect required field in map with AllowPartial",
mo: protojson.MarshalOptions{AllowPartial: true},
input: &pb2.IndirectRequired{
StrToNested: map[string]*pb2.NestedWithRequired{
"fail": &pb2.NestedWithRequired{},
},
},
want: `{
"strToNested": {
"fail": {}
}
}`,
}, {
desc: "indirect required field in oneof",
input: &pb2.IndirectRequired{
Union: &pb2.IndirectRequired_OneofNested{
OneofNested: &pb2.NestedWithRequired{},
},
},
want: `{
"oneofNested": {}
}`,
wantErr: true,
}, {
desc: "indirect required field in oneof with AllowPartial",
mo: protojson.MarshalOptions{AllowPartial: true},
input: &pb2.IndirectRequired{
Union: &pb2.IndirectRequired_OneofNested{
OneofNested: &pb2.NestedWithRequired{},
},
},
want: `{
"oneofNested": {}
}`,
}, {
desc: "unknown fields are ignored",
input: func() proto.Message {
m := &pb2.Scalars{
OptString: proto.String("no unknowns"),
}
m.ProtoReflect().SetUnknown(protopack.Message{
protopack.Tag{101, protopack.BytesType}, protopack.String("hello world"),
}.Marshal())
return m
}(),
want: `{
"optString": "no unknowns"
}`,
}, {
desc: "json_name",
input: &pb3.JSONNames{
SString: "json_name",
},
want: `{
"foo_bar": "json_name"
}`,
}, {
desc: "extensions of non-repeated fields",
input: func() proto.Message {
m := &pb2.Extensions{
OptString: proto.String("non-extension field"),
OptBool: proto.Bool(true),
OptInt32: proto.Int32(42),
}
proto.SetExtension(m, pb2.E_OptExtBool, true)
proto.SetExtension(m, pb2.E_OptExtString, "extension field")
proto.SetExtension(m, pb2.E_OptExtEnum, pb2.Enum_TEN)
proto.SetExtension(m, pb2.E_OptExtNested, &pb2.Nested{
OptString: proto.String("nested in an extension"),
OptNested: &pb2.Nested{
OptString: proto.String("another nested in an extension"),
},
})
return m
}(),
want: `{
"optString": "non-extension field",
"optBool": true,
"optInt32": 42,
"[pb2.opt_ext_bool]": true,
"[pb2.opt_ext_enum]": "TEN",
"[pb2.opt_ext_nested]": {
"optString": "nested in an extension",
"optNested": {
"optString": "another nested in an extension"
}
},
"[pb2.opt_ext_string]": "extension field"
}`,
}, {
desc: "extensions of repeated fields",
input: func() proto.Message {
m := &pb2.Extensions{}
proto.SetExtension(m, pb2.E_RptExtEnum, []pb2.Enum{pb2.Enum_TEN, 101, pb2.Enum_ONE})
proto.SetExtension(m, pb2.E_RptExtFixed32, []uint32{42, 47})
proto.SetExtension(m, pb2.E_RptExtNested, []*pb2.Nested{
&pb2.Nested{OptString: proto.String("one")},
&pb2.Nested{OptString: proto.String("two")},
&pb2.Nested{OptString: proto.String("three")},
})
return m
}(),
want: `{
"[pb2.rpt_ext_enum]": [
"TEN",
101,
"ONE"
],
"[pb2.rpt_ext_fixed32]": [
42,
47
],
"[pb2.rpt_ext_nested]": [
{
"optString": "one"
},
{
"optString": "two"
},
{
"optString": "three"
}
]
}`,
}, {
desc: "extensions of non-repeated fields in another message",
input: func() proto.Message {
m := &pb2.Extensions{}
proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtBool, true)
proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtString, "extension field")
proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtEnum, pb2.Enum_TEN)
proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtNested, &pb2.Nested{
OptString: proto.String("nested in an extension"),
OptNested: &pb2.Nested{
OptString: proto.String("another nested in an extension"),
},
})
return m
}(),
want: `{
"[pb2.ExtensionsContainer.opt_ext_bool]": true,
"[pb2.ExtensionsContainer.opt_ext_enum]": "TEN",
"[pb2.ExtensionsContainer.opt_ext_nested]": {
"optString": "nested in an extension",
"optNested": {
"optString": "another nested in an extension"
}
},
"[pb2.ExtensionsContainer.opt_ext_string]": "extension field"
}`,
}, {
desc: "extensions of repeated fields in another message",
input: func() proto.Message {
m := &pb2.Extensions{
OptString: proto.String("non-extension field"),
OptBool: proto.Bool(true),
OptInt32: proto.Int32(42),
}
proto.SetExtension(m, pb2.E_ExtensionsContainer_RptExtEnum, []pb2.Enum{pb2.Enum_TEN, 101, pb2.Enum_ONE})
proto.SetExtension(m, pb2.E_ExtensionsContainer_RptExtString, []string{"hello", "world"})
proto.SetExtension(m, pb2.E_ExtensionsContainer_RptExtNested, []*pb2.Nested{
&pb2.Nested{OptString: proto.String("one")},
&pb2.Nested{OptString: proto.String("two")},
&pb2.Nested{OptString: proto.String("three")},
})
return m
}(),
want: `{
"optString": "non-extension field",
"optBool": true,
"optInt32": 42,
"[pb2.ExtensionsContainer.rpt_ext_enum]": [
"TEN",
101,
"ONE"
],
"[pb2.ExtensionsContainer.rpt_ext_nested]": [
{
"optString": "one"
},
{
"optString": "two"
},
{
"optString": "three"
}
],
"[pb2.ExtensionsContainer.rpt_ext_string]": [
"hello",
"world"
]
}`,
}, {
desc: "MessageSet",
input: func() proto.Message {
m := &pb2.MessageSet{}
proto.SetExtension(m, pb2.E_MessageSetExtension_MessageSetExtension, &pb2.MessageSetExtension{
OptString: proto.String("a messageset extension"),
})
proto.SetExtension(m, pb2.E_MessageSetExtension_NotMessageSetExtension, &pb2.MessageSetExtension{
OptString: proto.String("not a messageset extension"),
})
proto.SetExtension(m, pb2.E_MessageSetExtension_ExtNested, &pb2.Nested{
OptString: proto.String("just a regular extension"),
})
return m
}(),
want: `{
"[pb2.MessageSetExtension.ext_nested]": {
"optString": "just a regular extension"
},
"[pb2.MessageSetExtension]": {
"optString": "a messageset extension"
},
"[pb2.MessageSetExtension.not_message_set_extension]": {
"optString": "not a messageset extension"
}
}`,
skip: !flags.ProtoLegacy,
}, {
desc: "not real MessageSet 1",
input: func() proto.Message {
m := &pb2.FakeMessageSet{}
proto.SetExtension(m, pb2.E_FakeMessageSetExtension_MessageSetExtension, &pb2.FakeMessageSetExtension{
OptString: proto.String("not a messageset extension"),
})
return m
}(),
want: `{
"[pb2.FakeMessageSetExtension.message_set_extension]": {
"optString": "not a messageset extension"
}
}`,
skip: !flags.ProtoLegacy,
}, {
desc: "not real MessageSet 2",
input: func() proto.Message {
m := &pb2.MessageSet{}
proto.SetExtension(m, pb2.E_MessageSetExtension, &pb2.FakeMessageSetExtension{
OptString: proto.String("another not a messageset extension"),
})
return m
}(),
want: `{
"[pb2.message_set_extension]": {
"optString": "another not a messageset extension"
}
}`,
skip: !flags.ProtoLegacy,
}, {
desc: "BoolValue empty",
input: &wrapperspb.BoolValue{},
want: `false`,
}, {
desc: "BoolValue",
input: &wrapperspb.BoolValue{Value: true},
want: `true`,
}, {
desc: "Int32Value empty",
input: &wrapperspb.Int32Value{},
want: `0`,
}, {
desc: "Int32Value",
input: &wrapperspb.Int32Value{Value: 42},
want: `42`,
}, {
desc: "Int64Value",
input: &wrapperspb.Int64Value{Value: 42},
want: `"42"`,
}, {
desc: "UInt32Value",
input: &wrapperspb.UInt32Value{Value: 42},
want: `42`,
}, {
desc: "UInt64Value",
input: &wrapperspb.UInt64Value{Value: 42},
want: `"42"`,
}, {
desc: "FloatValue",
input: &wrapperspb.FloatValue{Value: 1.02},
want: `1.02`,
}, {
desc: "FloatValue Infinity",
input: &wrapperspb.FloatValue{Value: float32(math.Inf(-1))},
want: `"-Infinity"`,
}, {
desc: "DoubleValue",
input: &wrapperspb.DoubleValue{Value: 1.02},
want: `1.02`,
}, {
desc: "DoubleValue NaN",
input: &wrapperspb.DoubleValue{Value: math.NaN()},
want: `"NaN"`,
}, {
desc: "StringValue empty",
input: &wrapperspb.StringValue{},
want: `""`,
}, {
desc: "StringValue",
input: &wrapperspb.StringValue{Value: "谷歌"},
want: `"谷歌"`,
}, {
desc: "StringValue with invalid UTF8 error",
input: &wrapperspb.StringValue{Value: "abc\xff"},
wantErr: true,
}, {
desc: "StringValue field with invalid UTF8 error",
input: &pb2.KnownTypes{
OptString: &wrapperspb.StringValue{Value: "abc\xff"},
},
wantErr: true,
}, {
desc: "BytesValue",
input: &wrapperspb.BytesValue{Value: []byte("hello")},
want: `"aGVsbG8="`,
}, {
desc: "Empty",
input: &emptypb.Empty{},
want: `{}`,
}, {
desc: "NullValue field",
input: &pb2.KnownTypes{OptNull: new(structpb.NullValue)},
want: `{
"optNull": null
}`,
}, {
desc: "Value empty",
input: &structpb.Value{},
wantErr: true,
}, {
desc: "Value empty field",
input: &pb2.KnownTypes{
OptValue: &structpb.Value{},
},
wantErr: true,
}, {
desc: "Value contains NullValue",
input: &structpb.Value{Kind: &structpb.Value_NullValue{}},
want: `null`,
}, {
desc: "Value contains BoolValue",
input: &structpb.Value{Kind: &structpb.Value_BoolValue{}},
want: `false`,
}, {
desc: "Value contains NumberValue",
input: &structpb.Value{Kind: &structpb.Value_NumberValue{1.02}},
want: `1.02`,
}, {
desc: "Value contains StringValue",
input: &structpb.Value{Kind: &structpb.Value_StringValue{"hello"}},
want: `"hello"`,
}, {
desc: "Value contains StringValue with invalid UTF8",
input: &structpb.Value{Kind: &structpb.Value_StringValue{"\xff"}},
wantErr: true,
}, {
desc: "Value contains Struct",
input: &structpb.Value{
Kind: &structpb.Value_StructValue{
&structpb.Struct{
Fields: map[string]*structpb.Value{
"null": {Kind: &structpb.Value_NullValue{}},
"number": {Kind: &structpb.Value_NumberValue{}},
"string": {Kind: &structpb.Value_StringValue{}},
"struct": {Kind: &structpb.Value_StructValue{}},
"list": {Kind: &structpb.Value_ListValue{}},
"bool": {Kind: &structpb.Value_BoolValue{}},
},
},
},
},
want: `{
"bool": false,
"list": [],
"null": null,
"number": 0,
"string": "",
"struct": {}
}`,
}, {
desc: "Value contains ListValue",
input: &structpb.Value{
Kind: &structpb.Value_ListValue{
&structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_BoolValue{}},
{Kind: &structpb.Value_NullValue{}},
{Kind: &structpb.Value_NumberValue{}},
{Kind: &structpb.Value_StringValue{}},
{Kind: &structpb.Value_StructValue{}},
{Kind: &structpb.Value_ListValue{}},
},
},
},
},
want: `[
false,
null,
0,
"",
{},
[]
]`,
}, {
desc: "Value with NaN",
input: structpb.NewNumberValue(math.NaN()),
wantErr: true,
}, {
desc: "Value with -Inf",
input: structpb.NewNumberValue(math.Inf(-1)),
wantErr: true,
}, {
desc: "Value with +Inf",
input: structpb.NewNumberValue(math.Inf(+1)),
wantErr: true,
}, {
desc: "Struct with nil map",
input: &structpb.Struct{},
want: `{}`,
}, {
desc: "Struct with empty map",
input: &structpb.Struct{
Fields: map[string]*structpb.Value{},
},
want: `{}`,
}, {
desc: "Struct",
input: &structpb.Struct{
Fields: map[string]*structpb.Value{
"bool": {Kind: &structpb.Value_BoolValue{true}},
"null": {Kind: &structpb.Value_NullValue{}},
"number": {Kind: &structpb.Value_NumberValue{3.1415}},
"string": {Kind: &structpb.Value_StringValue{"hello"}},
"struct": {
Kind: &structpb.Value_StructValue{
&structpb.Struct{
Fields: map[string]*structpb.Value{
"string": {Kind: &structpb.Value_StringValue{"world"}},
},
},
},
},
"list": {
Kind: &structpb.Value_ListValue{
&structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_BoolValue{}},
{Kind: &structpb.Value_NullValue{}},
{Kind: &structpb.Value_NumberValue{}},
},
},
},
},
},
},
want: `{
"bool": true,
"list": [
false,
null,
0
],
"null": null,
"number": 3.1415,
"string": "hello",
"struct": {
"string": "world"
}
}`,
}, {
desc: "Struct message with invalid UTF8 string",
input: &structpb.Struct{
Fields: map[string]*structpb.Value{
"string": {Kind: &structpb.Value_StringValue{"\xff"}},
},
},
wantErr: true,
}, {
desc: "ListValue with nil values",
input: &structpb.ListValue{},
want: `[]`,
}, {
desc: "ListValue with empty values",
input: &structpb.ListValue{
Values: []*structpb.Value{},
},
want: `[]`,
}, {
desc: "ListValue",
input: &structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_BoolValue{true}},
{Kind: &structpb.Value_NullValue{}},
{Kind: &structpb.Value_NumberValue{3.1415}},
{Kind: &structpb.Value_StringValue{"hello"}},
{
Kind: &structpb.Value_ListValue{
&structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_BoolValue{}},
{Kind: &structpb.Value_NullValue{}},
{Kind: &structpb.Value_NumberValue{}},
},
},
},
},
{
Kind: &structpb.Value_StructValue{
&structpb.Struct{
Fields: map[string]*structpb.Value{
"string": {Kind: &structpb.Value_StringValue{"world"}},
},
},
},
},
},
},
want: `[
true,
null,
3.1415,
"hello",
[
false,
null,
0
],
{
"string": "world"
}
]`,
}, {
desc: "ListValue with invalid UTF8 string",
input: &structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_StringValue{"\xff"}},
},
},
wantErr: true,
}, {
desc: "Duration empty",
input: &durationpb.Duration{},
want: `"0s"`,
}, {
desc: "Duration with secs",
input: &durationpb.Duration{Seconds: 3},
want: `"3s"`,
}, {
desc: "Duration with -secs",
input: &durationpb.Duration{Seconds: -3},
want: `"-3s"`,
}, {
desc: "Duration with nanos",
input: &durationpb.Duration{Nanos: 1e6},
want: `"0.001s"`,
}, {
desc: "Duration with -nanos",
input: &durationpb.Duration{Nanos: -1e6},
want: `"-0.001s"`,
}, {
desc: "Duration with large secs",
input: &durationpb.Duration{Seconds: 1e10, Nanos: 1},
want: `"10000000000.000000001s"`,
}, {
desc: "Duration with 6-digit nanos",
input: &durationpb.Duration{Nanos: 1e4},
want: `"0.000010s"`,
}, {
desc: "Duration with 3-digit nanos",
input: &durationpb.Duration{Nanos: 1e6},
want: `"0.001s"`,
}, {
desc: "Duration with -secs -nanos",
input: &durationpb.Duration{Seconds: -123, Nanos: -450},
want: `"-123.000000450s"`,
}, {
desc: "Duration max value",
input: &durationpb.Duration{Seconds: 315576000000, Nanos: 999999999},
want: `"315576000000.999999999s"`,
}, {
desc: "Duration min value",
input: &durationpb.Duration{Seconds: -315576000000, Nanos: -999999999},
want: `"-315576000000.999999999s"`,
}, {
desc: "Duration with +secs -nanos",
input: &durationpb.Duration{Seconds: 1, Nanos: -1},
wantErr: true,
}, {
desc: "Duration with -secs +nanos",
input: &durationpb.Duration{Seconds: -1, Nanos: 1},
wantErr: true,
}, {
desc: "Duration with +secs out of range",
input: &durationpb.Duration{Seconds: 315576000001},
wantErr: true,
}, {
desc: "Duration with -secs out of range",
input: &durationpb.Duration{Seconds: -315576000001},
wantErr: true,
}, {
desc: "Duration with +nanos out of range",
input: &durationpb.Duration{Seconds: 0, Nanos: 1e9},
wantErr: true,
}, {
desc: "Duration with -nanos out of range",
input: &durationpb.Duration{Seconds: 0, Nanos: -1e9},
wantErr: true,
}, {
desc: "Timestamp zero",
input: &timestamppb.Timestamp{},
want: `"1970-01-01T00:00:00Z"`,
}, {
desc: "Timestamp",
input: &timestamppb.Timestamp{Seconds: 1553036601},
want: `"2019-03-19T23:03:21Z"`,
}, {
desc: "Timestamp with nanos",
input: &timestamppb.Timestamp{Seconds: 1553036601, Nanos: 1},
want: `"2019-03-19T23:03:21.000000001Z"`,
}, {
desc: "Timestamp with 6-digit nanos",
input: &timestamppb.Timestamp{Nanos: 1e3},
want: `"1970-01-01T00:00:00.000001Z"`,
}, {
desc: "Timestamp with 3-digit nanos",
input: &timestamppb.Timestamp{Nanos: 1e7},
want: `"1970-01-01T00:00:00.010Z"`,
}, {
desc: "Timestamp max value",
input: &timestamppb.Timestamp{Seconds: 253402300799, Nanos: 999999999},
want: `"9999-12-31T23:59:59.999999999Z"`,
}, {
desc: "Timestamp min value",
input: &timestamppb.Timestamp{Seconds: -62135596800},
want: `"0001-01-01T00:00:00Z"`,
}, {
desc: "Timestamp with +secs out of range",
input: &timestamppb.Timestamp{Seconds: 253402300800},
wantErr: true,
}, {
desc: "Timestamp with -secs out of range",
input: &timestamppb.Timestamp{Seconds: -62135596801},
wantErr: true,
}, {
desc: "Timestamp with -nanos",
input: &timestamppb.Timestamp{Nanos: -1},
wantErr: true,
}, {
desc: "Timestamp with +nanos out of range",
input: &timestamppb.Timestamp{Nanos: 1e9},
wantErr: true,
}, {
desc: "FieldMask empty",
input: &fieldmaskpb.FieldMask{},
want: `""`,
}, {
desc: "FieldMask",
input: &fieldmaskpb.FieldMask{
Paths: []string{
"foo",
"foo_bar",
"foo.bar_qux",
"_foo",
},
},
want: `"foo,fooBar,foo.barQux,Foo"`,
}, {
desc: "FieldMask empty string path",
input: &fieldmaskpb.FieldMask{
Paths: []string{""},
},
wantErr: true,
}, {
desc: "FieldMask path contains spaces only",
input: &fieldmaskpb.FieldMask{
Paths: []string{" "},
},
wantErr: true,
}, {
desc: "FieldMask irreversible error 1",
input: &fieldmaskpb.FieldMask{
Paths: []string{"foo_"},
},
wantErr: true,
}, {
desc: "FieldMask irreversible error 2",
input: &fieldmaskpb.FieldMask{
Paths: []string{"foo__bar"},
},
wantErr: true,
}, {
desc: "FieldMask invalid char",
input: &fieldmaskpb.FieldMask{
Paths: []string{"foo@bar"},
},
wantErr: true,
}, {
desc: "Any empty",
input: &anypb.Any{},
want: `{}`,
}, {
desc: "Any with non-custom message",
input: func() proto.Message {
m := &pb2.Nested{
OptString: proto.String("embedded inside Any"),
OptNested: &pb2.Nested{
OptString: proto.String("inception"),
},
}
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
if err != nil {
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
}
return &anypb.Any{
TypeUrl: "foo/pb2.Nested",
Value: b,
}
}(),
want: `{
"@type": "foo/pb2.Nested",
"optString": "embedded inside Any",
"optNested": {
"optString": "inception"
}
}`,
}, {
desc: "Any with empty embedded message",
input: &anypb.Any{TypeUrl: "foo/pb2.Nested"},
want: `{
"@type": "foo/pb2.Nested"
}`,
}, {
desc: "Any without registered type",
mo: protojson.MarshalOptions{Resolver: new(protoregistry.Types)},
input: &anypb.Any{TypeUrl: "foo/pb2.Nested"},
wantErr: true,
}, {
desc: "Any with missing required",
input: func() proto.Message {
m := &pb2.PartialRequired{
OptString: proto.String("embedded inside Any"),
}
b, err := proto.MarshalOptions{
AllowPartial: true,
Deterministic: true,
}.Marshal(m)
if err != nil {
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
}
return &anypb.Any{
TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
Value: b,
}
}(),
want: `{
"@type": "pb2.PartialRequired",
"optString": "embedded inside Any"
}`,
}, {
desc: "Any with partial required and AllowPartial",
mo: protojson.MarshalOptions{
AllowPartial: true,
},
input: func() proto.Message {
m := &pb2.PartialRequired{
OptString: proto.String("embedded inside Any"),
}
b, err := proto.MarshalOptions{
AllowPartial: true,
Deterministic: true,
}.Marshal(m)
if err != nil {
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
}
return &anypb.Any{
TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
Value: b,
}
}(),
want: `{
"@type": "pb2.PartialRequired",
"optString": "embedded inside Any"
}`,
}, {
desc: "Any with EmitUnpopulated",
mo: protojson.MarshalOptions{
EmitUnpopulated: true,
},
input: func() proto.Message {
return &anypb.Any{
TypeUrl: string(new(pb3.Scalars).ProtoReflect().Descriptor().FullName()),
}
}(),
want: `{
"@type": "pb3.Scalars",
"sBool": false,
"sInt32": 0,
"sInt64": "0",
"sUint32": 0,
"sUint64": "0",
"sSint32": 0,
"sSint64": "0",
"sFixed32": 0,
"sFixed64": "0",
"sSfixed32": 0,
"sSfixed64": "0",
"sFloat": 0,
"sDouble": 0,
"sBytes": "",
"sString": ""
}`,
}, {
desc: "Any with invalid UTF8",
input: func() proto.Message {
m := &pb2.Nested{
OptString: proto.String("abc\xff"),
}
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
if err != nil {
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
}
return &anypb.Any{
TypeUrl: "foo/pb2.Nested",
Value: b,
}
}(),
wantErr: true,
}, {
desc: "Any with invalid value",
input: &anypb.Any{
TypeUrl: "foo/pb2.Nested",
Value: []byte("\x80"),
},
wantErr: true,
}, {
desc: "Any with BoolValue",
input: func() proto.Message {
m := &wrapperspb.BoolValue{Value: true}
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
if err != nil {
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
}
return &anypb.Any{
TypeUrl: "type.googleapis.com/google.protobuf.BoolValue",
Value: b,
}
}(),
want: `{
"@type": "type.googleapis.com/google.protobuf.BoolValue",
"value": true
}`,
}, {
desc: "Any with Empty",
input: func() proto.Message {
m := &emptypb.Empty{}
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
if err != nil {
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
}
return &anypb.Any{
TypeUrl: "type.googleapis.com/google.protobuf.Empty",
Value: b,
}
}(),
want: `{
"@type": "type.googleapis.com/google.protobuf.Empty",
"value": {}
}`,
}, {
desc: "Any with StringValue containing invalid UTF8",
input: func() proto.Message {
m := &wrapperspb.StringValue{Value: "abcd"}
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
if err != nil {
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
}
return &anypb.Any{
TypeUrl: "google.protobuf.StringValue",
Value: bytes.Replace(b, []byte("abcd"), []byte("abc\xff"), -1),
}
}(),
wantErr: true,
}, {
desc: "Any with Int64Value",
input: func() proto.Message {
m := &wrapperspb.Int64Value{Value: 42}
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
if err != nil {
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
}
return &anypb.Any{
TypeUrl: "google.protobuf.Int64Value",
Value: b,
}
}(),
want: `{
"@type": "google.protobuf.Int64Value",
"value": "42"
}`,
}, {
desc: "Any with Duration",
input: func() proto.Message {
m := &durationpb.Duration{}
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
if err != nil {
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
}
return &anypb.Any{
TypeUrl: "type.googleapis.com/google.protobuf.Duration",
Value: b,
}
}(),
want: `{
"@type": "type.googleapis.com/google.protobuf.Duration",
"value": "0s"
}`,
}, {
desc: "Any with empty Value",
input: func() proto.Message {
m := &structpb.Value{}
b, err := proto.Marshal(m)
if err != nil {
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
}
return &anypb.Any{
TypeUrl: "type.googleapis.com/google.protobuf.Value",
Value: b,
}
}(),
wantErr: true,
}, {
desc: "Any with Value of StringValue",
input: func() proto.Message {
m := &structpb.Value{Kind: &structpb.Value_StringValue{"abcd"}}
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
if err != nil {
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
}
return &anypb.Any{
TypeUrl: "type.googleapis.com/google.protobuf.Value",
Value: bytes.Replace(b, []byte("abcd"), []byte("abc\xff"), -1),
}
}(),
wantErr: true,
}, {
desc: "Any with Value of NullValue",
input: func() proto.Message {
m := &structpb.Value{Kind: &structpb.Value_NullValue{}}
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
if err != nil {
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
}
return &anypb.Any{
TypeUrl: "type.googleapis.com/google.protobuf.Value",
Value: b,
}
}(),
want: `{
"@type": "type.googleapis.com/google.protobuf.Value",
"value": null
}`,
}, {
desc: "Any with Struct",
input: func() proto.Message {
m := &structpb.Struct{
Fields: map[string]*structpb.Value{
"bool": {Kind: &structpb.Value_BoolValue{true}},
"null": {Kind: &structpb.Value_NullValue{}},
"string": {Kind: &structpb.Value_StringValue{"hello"}},
"struct": {
Kind: &structpb.Value_StructValue{
&structpb.Struct{
Fields: map[string]*structpb.Value{
"string": {Kind: &structpb.Value_StringValue{"world"}},
},
},
},
},
},
}
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
if err != nil {
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
}
return &anypb.Any{
TypeUrl: "google.protobuf.Struct",
Value: b,
}
}(),
want: `{
"@type": "google.protobuf.Struct",
"value": {
"bool": true,
"null": null,
"string": "hello",
"struct": {
"string": "world"
}
}
}`,
}, {
desc: "Any with missing type_url",
input: func() proto.Message {
m := &wrapperspb.BoolValue{Value: true}
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
if err != nil {
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
}
return &anypb.Any{
Value: b,
}
}(),
wantErr: true,
}, {
desc: "well known types as field values",
input: &pb2.KnownTypes{
OptBool: &wrapperspb.BoolValue{Value: false},
OptInt32: &wrapperspb.Int32Value{Value: 42},
OptInt64: &wrapperspb.Int64Value{Value: 42},
OptUint32: &wrapperspb.UInt32Value{Value: 42},
OptUint64: &wrapperspb.UInt64Value{Value: 42},
OptFloat: &wrapperspb.FloatValue{Value: 1.23},
OptDouble: &wrapperspb.DoubleValue{Value: 3.1415},
OptString: &wrapperspb.StringValue{Value: "hello"},
OptBytes: &wrapperspb.BytesValue{Value: []byte("hello")},
OptDuration: &durationpb.Duration{Seconds: 123},
OptTimestamp: &timestamppb.Timestamp{Seconds: 1553036601},
OptStruct: &structpb.Struct{
Fields: map[string]*structpb.Value{
"string": {Kind: &structpb.Value_StringValue{"hello"}},
},
},
OptList: &structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_NullValue{}},
{Kind: &structpb.Value_StringValue{}},
{Kind: &structpb.Value_StructValue{}},
{Kind: &structpb.Value_ListValue{}},
},
},
OptValue: &structpb.Value{
Kind: &structpb.Value_StringValue{"world"},
},
OptEmpty: &emptypb.Empty{},
OptAny: &anypb.Any{
TypeUrl: "google.protobuf.Empty",
},
OptFieldmask: &fieldmaskpb.FieldMask{
Paths: []string{"foo_bar", "bar_foo"},
},
},
want: `{
"optBool": false,
"optInt32": 42,
"optInt64": "42",
"optUint32": 42,
"optUint64": "42",
"optFloat": 1.23,
"optDouble": 3.1415,
"optString": "hello",
"optBytes": "aGVsbG8=",
"optDuration": "123s",
"optTimestamp": "2019-03-19T23:03:21Z",
"optStruct": {
"string": "hello"
},
"optList": [
null,
"",
{},
[]
],
"optValue": "world",
"optEmpty": {},
"optAny": {
"@type": "google.protobuf.Empty",
"value": {}
},
"optFieldmask": "fooBar,barFoo"
}`,
}, {
desc: "EmitUnpopulated: proto2 optional scalars",
mo: protojson.MarshalOptions{EmitUnpopulated: true},
input: &pb2.Scalars{},
want: `{
"optBool": null,
"optInt32": null,
"optInt64": null,
"optUint32": null,
"optUint64": null,
"optSint32": null,
"optSint64": null,
"optFixed32": null,
"optFixed64": null,
"optSfixed32": null,
"optSfixed64": null,
"optFloat": null,
"optDouble": null,
"optBytes": null,
"optString": null
}`,
}, {
desc: "EmitUnpopulated: proto3 scalars",
mo: protojson.MarshalOptions{EmitUnpopulated: true},
input: &pb3.Scalars{},
want: `{
"sBool": false,
"sInt32": 0,
"sInt64": "0",
"sUint32": 0,
"sUint64": "0",
"sSint32": 0,
"sSint64": "0",
"sFixed32": 0,
"sFixed64": "0",
"sSfixed32": 0,
"sSfixed64": "0",
"sFloat": 0,
"sDouble": 0,
"sBytes": "",
"sString": ""
}`,
}, {
desc: "EmitUnpopulated: proto2 enum",
mo: protojson.MarshalOptions{EmitUnpopulated: true},
input: &pb2.Enums{},
want: `{
"optEnum": null,
"rptEnum": [],
"optNestedEnum": null,
"rptNestedEnum": []
}`,
}, {
desc: "EmitUnpopulated: proto3 enum",
mo: protojson.MarshalOptions{EmitUnpopulated: true},
input: &pb3.Enums{},
want: `{
"sEnum": "ZERO",
"sNestedEnum": "CERO"
}`,
}, {
desc: "EmitUnpopulated: proto2 message and group fields",
mo: protojson.MarshalOptions{EmitUnpopulated: true},
input: &pb2.Nests{},
want: `{
"optNested": null,
"optgroup": null,
"rptNested": [],
"rptgroup": []
}`,
}, {
desc: "EmitUnpopulated: proto3 message field",
mo: protojson.MarshalOptions{EmitUnpopulated: true},
input: &pb3.Nests{},
want: `{
"sNested": null
}`,
}, {
desc: "EmitUnpopulated: proto2 empty message and group fields",
mo: protojson.MarshalOptions{EmitUnpopulated: true},
input: &pb2.Nests{
OptNested: &pb2.Nested{},
Optgroup: &pb2.Nests_OptGroup{},
},
want: `{
"optNested": {
"optString": null,
"optNested": null
},
"optgroup": {
"optString": null,
"optNested": null,
"optnestedgroup": null
},
"rptNested": [],
"rptgroup": []
}`,
}, {
desc: "EmitUnpopulated: proto3 empty message field",
mo: protojson.MarshalOptions{EmitUnpopulated: true},
input: &pb3.Nests{
SNested: &pb3.Nested{},
},
want: `{
"sNested": {
"sString": "",
"sNested": null
}
}`,
}, {
desc: "EmitUnpopulated: proto2 required fields",
mo: protojson.MarshalOptions{
AllowPartial: true,
EmitUnpopulated: true,
},
input: &pb2.Requireds{},
want: `{
"reqBool": null,
"reqSfixed64": null,
"reqDouble": null,
"reqString": null,
"reqEnum": null,
"reqNested": null
}`,
}, {
desc: "EmitUnpopulated: repeated fields",
mo: protojson.MarshalOptions{EmitUnpopulated: true},
input: &pb2.Repeats{},
want: `{
"rptBool": [],
"rptInt32": [],
"rptInt64": [],
"rptUint32": [],
"rptUint64": [],
"rptFloat": [],
"rptDouble": [],
"rptString": [],
"rptBytes": []
}`,
}, {
desc: "EmitUnpopulated: repeated containing empty message",
mo: protojson.MarshalOptions{EmitUnpopulated: true},
input: &pb2.Nests{
RptNested: []*pb2.Nested{nil, {}},
},
want: `{
"optNested": null,
"optgroup": null,
"rptNested": [
{
"optString": null,
"optNested": null
},
{
"optString": null,
"optNested": null
}
],
"rptgroup": []
}`,
}, {
desc: "EmitUnpopulated: map fields",
mo: protojson.MarshalOptions{EmitUnpopulated: true},
input: &pb3.Maps{},
want: `{
"int32ToStr": {},
"boolToUint32": {},
"uint64ToEnum": {},
"strToNested": {},
"strToOneofs": {}
}`,
}, {
desc: "EmitUnpopulated: map containing empty message",
mo: protojson.MarshalOptions{EmitUnpopulated: true},
input: &pb3.Maps{
StrToNested: map[string]*pb3.Nested{
"nested": &pb3.Nested{},
},
StrToOneofs: map[string]*pb3.Oneofs{
"nested": &pb3.Oneofs{},
},
},
want: `{
"int32ToStr": {},
"boolToUint32": {},
"uint64ToEnum": {},
"strToNested": {
"nested": {
"sString": "",
"sNested": null
}
},
"strToOneofs": {
"nested": {}
}
}`,
}, {
desc: "EmitUnpopulated: oneof fields",
mo: protojson.MarshalOptions{EmitUnpopulated: true},
input: &pb3.Oneofs{},
want: `{}`,
}, {
desc: "EmitUnpopulated: extensions",
mo: protojson.MarshalOptions{EmitUnpopulated: true},
input: func() proto.Message {
m := &pb2.Extensions{}
proto.SetExtension(m, pb2.E_OptExtNested, &pb2.Nested{})
proto.SetExtension(m, pb2.E_RptExtNested, []*pb2.Nested{
nil,
{},
})
return m
}(),
want: `{
"optString": null,
"optBool": null,
"optInt32": null,
"[pb2.opt_ext_nested]": {
"optString": null,
"optNested": null
},
"[pb2.rpt_ext_nested]": [
{
"optString": null,
"optNested": null
},
{
"optString": null,
"optNested": null
}
]
}`,
}, {
desc: "EmitUnpopulated: with populated fields",
mo: protojson.MarshalOptions{EmitUnpopulated: true},
input: &pb2.Scalars{
OptInt32: proto.Int32(0xff),
OptUint32: proto.Uint32(47),
OptSint32: proto.Int32(-1001),
OptFixed32: proto.Uint32(32),
OptSfixed32: proto.Int32(-32),
OptFloat: proto.Float32(1.02),
OptBytes: []byte("谷歌"),
},
want: `{
"optBool": null,
"optInt32": 255,
"optInt64": null,
"optUint32": 47,
"optUint64": null,
"optSint32": -1001,
"optSint64": null,
"optFixed32": 32,
"optFixed64": null,
"optSfixed32": -32,
"optSfixed64": null,
"optFloat": 1.02,
"optDouble": null,
"optBytes": "6LC35q2M",
"optString": null
}`,
}, {
desc: "UseEnumNumbers in singular field",
mo: protojson.MarshalOptions{UseEnumNumbers: true},
input: &pb2.Enums{
OptEnum: pb2.Enum_ONE.Enum(),
OptNestedEnum: pb2.Enums_UNO.Enum(),
},
want: `{
"optEnum": 1,
"optNestedEnum": 1
}`,
}, {
desc: "UseEnumNumbers in repeated field",
mo: protojson.MarshalOptions{UseEnumNumbers: true},
input: &pb2.Enums{
RptEnum: []pb2.Enum{pb2.Enum_ONE, 2, pb2.Enum_TEN, 42},
RptNestedEnum: []pb2.Enums_NestedEnum{pb2.Enums_UNO, pb2.Enums_DOS, 47},
},
want: `{
"rptEnum": [
1,
2,
10,
42
],
"rptNestedEnum": [
1,
2,
47
]
}`,
}, {
desc: "UseEnumNumbers in map field",
mo: protojson.MarshalOptions{UseEnumNumbers: true},
input: &pb3.Maps{
Uint64ToEnum: map[uint64]pb3.Enum{
1: pb3.Enum_ONE,
2: pb3.Enum_TWO,
10: pb3.Enum_TEN,
47: 47,
},
},
want: `{
"uint64ToEnum": {
"1": 1,
"2": 2,
"10": 10,
"47": 47
}
}`,
}, {
desc: "UseProtoNames",
mo: protojson.MarshalOptions{UseProtoNames: true},
input: &pb2.Nests{
OptNested: &pb2.Nested{},
Optgroup: &pb2.Nests_OptGroup{
OptString: proto.String("inside a group"),
OptNested: &pb2.Nested{
OptString: proto.String("nested message inside a group"),
},
Optnestedgroup: &pb2.Nests_OptGroup_OptNestedGroup{
OptFixed32: proto.Uint32(47),
},
},
Rptgroup: []*pb2.Nests_RptGroup{
{
RptString: []string{"hello", "world"},
},
},
},
want: `{
"opt_nested": {},
"OptGroup": {
"opt_string": "inside a group",
"opt_nested": {
"opt_string": "nested message inside a group"
},
"OptNestedGroup": {
"opt_fixed32": 47
}
},
"RptGroup": [
{
"rpt_string": [
"hello",
"world"
]
}
]
}`,
}}
for _, tt := range tests {
tt := tt
if tt.skip {
continue
}
t.Run(tt.desc, func(t *testing.T) {
// Use 2-space indentation on all MarshalOptions.
tt.mo.Indent = " "
b, err := tt.mo.Marshal(tt.input)
if err != nil && !tt.wantErr {
t.Errorf("Marshal() returned error: %v\n", err)
}
if err == nil && tt.wantErr {
t.Errorf("Marshal() got nil error, want error\n")
}
got := string(b)
if got != tt.want {
t.Errorf("Marshal()\n<got>\n%v\n<want>\n%v\n", got, tt.want)
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("Marshal() diff -want +got\n%v\n", diff)
}
}
})
}
}