mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-03-26 02:37:29 +00:00
encoding: Add EmitDefaultValues option
Introduce the EmitDefaultValues in addition to the existing EmitUnpopulated option. EmitDefaultValues is added to emit json messages more compatible with the `always_print_primitive_fields` option of the cpp protobuf library. EmitUnpopulated overrides EmitDefaultValues since the former generates a strict superset of the latter. See descussion: https://github.com/golang/protobuf/issues/1536 Change-Id: Ib29b69d630fa3e8d8fdeb0de43b5683f30152151 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/521215 Reviewed-by: Damien Neil <dneil@google.com> Reviewed-by: Cassondra Foesch <cfoesch@gmail.com> Reviewed-by: Lasse Folger <lassefolger@google.com>
This commit is contained in:
parent
01c8445bb3
commit
8088bf85b8
@ -81,6 +81,25 @@ type MarshalOptions struct {
|
|||||||
// ╚═══════╧════════════════════════════╝
|
// ╚═══════╧════════════════════════════╝
|
||||||
EmitUnpopulated bool
|
EmitUnpopulated bool
|
||||||
|
|
||||||
|
// EmitDefaultValues specifies whether to emit default-valued primitive fields,
|
||||||
|
// empty lists, and empty maps. The fields affected are as follows:
|
||||||
|
// ╔═══════╤════════════════════════════════════════╗
|
||||||
|
// ║ JSON │ Protobuf field ║
|
||||||
|
// ╠═══════╪════════════════════════════════════════╣
|
||||||
|
// ║ false │ non-optional scalar boolean fields ║
|
||||||
|
// ║ 0 │ non-optional scalar numeric fields ║
|
||||||
|
// ║ "" │ non-optional scalar string/byte fields ║
|
||||||
|
// ║ [] │ empty repeated fields ║
|
||||||
|
// ║ {} │ empty map fields ║
|
||||||
|
// ╚═══════╧════════════════════════════════════════╝
|
||||||
|
//
|
||||||
|
// Behaves similarly to EmitUnpopulated, but does not emit "null"-value fields,
|
||||||
|
// i.e. presence-sensing fields that are omitted will remain omitted to preserve
|
||||||
|
// presence-sensing.
|
||||||
|
// EmitUnpopulated takes precedence over EmitDefaultValues since the former generates
|
||||||
|
// a strict superset of the latter.
|
||||||
|
EmitDefaultValues bool
|
||||||
|
|
||||||
// Resolver is used for looking up types when expanding google.protobuf.Any
|
// Resolver is used for looking up types when expanding google.protobuf.Any
|
||||||
// messages. If nil, this defaults to using protoregistry.GlobalTypes.
|
// messages. If nil, this defaults to using protoregistry.GlobalTypes.
|
||||||
Resolver interface {
|
Resolver interface {
|
||||||
@ -178,7 +197,11 @@ func (m typeURLFieldRanger) Range(f func(protoreflect.FieldDescriptor, protorefl
|
|||||||
|
|
||||||
// unpopulatedFieldRanger wraps a protoreflect.Message and modifies its Range
|
// unpopulatedFieldRanger wraps a protoreflect.Message and modifies its Range
|
||||||
// method to additionally iterate over unpopulated fields.
|
// method to additionally iterate over unpopulated fields.
|
||||||
type unpopulatedFieldRanger struct{ protoreflect.Message }
|
type unpopulatedFieldRanger struct {
|
||||||
|
protoreflect.Message
|
||||||
|
|
||||||
|
skipNull bool
|
||||||
|
}
|
||||||
|
|
||||||
func (m unpopulatedFieldRanger) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) {
|
func (m unpopulatedFieldRanger) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) {
|
||||||
fds := m.Descriptor().Fields()
|
fds := m.Descriptor().Fields()
|
||||||
@ -192,6 +215,9 @@ func (m unpopulatedFieldRanger) Range(f func(protoreflect.FieldDescriptor, proto
|
|||||||
isProto2Scalar := fd.Syntax() == protoreflect.Proto2 && fd.Default().IsValid()
|
isProto2Scalar := fd.Syntax() == protoreflect.Proto2 && fd.Default().IsValid()
|
||||||
isSingularMessage := fd.Cardinality() != protoreflect.Repeated && fd.Message() != nil
|
isSingularMessage := fd.Cardinality() != protoreflect.Repeated && fd.Message() != nil
|
||||||
if isProto2Scalar || isSingularMessage {
|
if isProto2Scalar || isSingularMessage {
|
||||||
|
if m.skipNull {
|
||||||
|
continue
|
||||||
|
}
|
||||||
v = protoreflect.Value{} // use invalid value to emit null
|
v = protoreflect.Value{} // use invalid value to emit null
|
||||||
}
|
}
|
||||||
if !f(fd, v) {
|
if !f(fd, v) {
|
||||||
@ -217,8 +243,11 @@ func (e encoder) marshalMessage(m protoreflect.Message, typeURL string) error {
|
|||||||
defer e.EndObject()
|
defer e.EndObject()
|
||||||
|
|
||||||
var fields order.FieldRanger = m
|
var fields order.FieldRanger = m
|
||||||
if e.opts.EmitUnpopulated {
|
switch {
|
||||||
fields = unpopulatedFieldRanger{m}
|
case e.opts.EmitUnpopulated:
|
||||||
|
fields = unpopulatedFieldRanger{Message: m, skipNull: false}
|
||||||
|
case e.opts.EmitDefaultValues:
|
||||||
|
fields = unpopulatedFieldRanger{Message: m, skipNull: true}
|
||||||
}
|
}
|
||||||
if typeURL != "" {
|
if typeURL != "" {
|
||||||
fields = typeURLFieldRanger{fields, typeURL}
|
fields = typeURLFieldRanger{fields, typeURL}
|
||||||
|
@ -2192,6 +2192,222 @@ func TestMarshal(t *testing.T) {
|
|||||||
"optDouble": null,
|
"optDouble": null,
|
||||||
"optBytes": "6LC35q2M",
|
"optBytes": "6LC35q2M",
|
||||||
"optString": null
|
"optString": null
|
||||||
|
}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitUnpopulated overrides EmitDefaultValues",
|
||||||
|
mo: protojson.MarshalOptions{EmitUnpopulated: true, EmitDefaultValues: true},
|
||||||
|
input: &pb2.Nests{
|
||||||
|
RptNested: []*pb2.Nested{nil, {}},
|
||||||
|
},
|
||||||
|
want: `{
|
||||||
|
"optNested": null,
|
||||||
|
"optgroup": null,
|
||||||
|
"rptNested": [
|
||||||
|
{
|
||||||
|
"optString": null,
|
||||||
|
"optNested": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optString": null,
|
||||||
|
"optNested": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rptgroup": []
|
||||||
|
}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitDefaultValues: proto2 optional scalars",
|
||||||
|
mo: protojson.MarshalOptions{EmitDefaultValues: true},
|
||||||
|
input: &pb2.Scalars{},
|
||||||
|
want: `{}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitDefaultValues: proto3 scalars",
|
||||||
|
mo: protojson.MarshalOptions{EmitDefaultValues: 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: "EmitDefaultValues: proto2 enum",
|
||||||
|
mo: protojson.MarshalOptions{EmitDefaultValues: true},
|
||||||
|
input: &pb2.Enums{},
|
||||||
|
want: `{
|
||||||
|
"rptEnum": [],
|
||||||
|
"rptNestedEnum": []
|
||||||
|
}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitDefaultValues: proto3 enum",
|
||||||
|
mo: protojson.MarshalOptions{EmitDefaultValues: true},
|
||||||
|
input: &pb3.Enums{},
|
||||||
|
want: `{
|
||||||
|
"sEnum": "ZERO",
|
||||||
|
"sNestedEnum": "CERO"
|
||||||
|
}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitDefaultValues: proto2 message and group fields",
|
||||||
|
mo: protojson.MarshalOptions{EmitDefaultValues: true},
|
||||||
|
input: &pb2.Nests{},
|
||||||
|
want: `{
|
||||||
|
"rptNested": [],
|
||||||
|
"rptgroup": []
|
||||||
|
}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitDefaultValues: proto3 message field",
|
||||||
|
mo: protojson.MarshalOptions{EmitDefaultValues: true},
|
||||||
|
input: &pb3.Nests{},
|
||||||
|
want: `{}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitDefaultValues: proto2 empty message and group fields",
|
||||||
|
mo: protojson.MarshalOptions{EmitDefaultValues: true},
|
||||||
|
input: &pb2.Nests{
|
||||||
|
OptNested: &pb2.Nested{},
|
||||||
|
Optgroup: &pb2.Nests_OptGroup{},
|
||||||
|
},
|
||||||
|
want: `{
|
||||||
|
"optNested": {},
|
||||||
|
"optgroup": {},
|
||||||
|
"rptNested": [],
|
||||||
|
"rptgroup": []
|
||||||
|
}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitDefaultValues: proto3 empty message field",
|
||||||
|
mo: protojson.MarshalOptions{EmitDefaultValues: true},
|
||||||
|
input: &pb3.Nests{
|
||||||
|
SNested: &pb3.Nested{},
|
||||||
|
},
|
||||||
|
want: `{
|
||||||
|
"sNested": {
|
||||||
|
"sString": ""
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitDefaultValues: proto2 required fields",
|
||||||
|
mo: protojson.MarshalOptions{
|
||||||
|
AllowPartial: true,
|
||||||
|
EmitDefaultValues: true,
|
||||||
|
},
|
||||||
|
input: &pb2.Requireds{},
|
||||||
|
want: `{}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitDefaultValues: repeated fields",
|
||||||
|
mo: protojson.MarshalOptions{EmitDefaultValues: true},
|
||||||
|
input: &pb2.Repeats{},
|
||||||
|
want: `{
|
||||||
|
"rptBool": [],
|
||||||
|
"rptInt32": [],
|
||||||
|
"rptInt64": [],
|
||||||
|
"rptUint32": [],
|
||||||
|
"rptUint64": [],
|
||||||
|
"rptFloat": [],
|
||||||
|
"rptDouble": [],
|
||||||
|
"rptString": [],
|
||||||
|
"rptBytes": []
|
||||||
|
}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitDefaultValues: repeated containing empty message",
|
||||||
|
mo: protojson.MarshalOptions{EmitDefaultValues: true},
|
||||||
|
input: &pb2.Nests{
|
||||||
|
RptNested: []*pb2.Nested{nil, {}},
|
||||||
|
},
|
||||||
|
want: `{
|
||||||
|
"rptNested": [
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
],
|
||||||
|
"rptgroup": []
|
||||||
|
}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitDefaultValues: map fields",
|
||||||
|
mo: protojson.MarshalOptions{EmitDefaultValues: true},
|
||||||
|
input: &pb3.Maps{},
|
||||||
|
want: `{
|
||||||
|
"int32ToStr": {},
|
||||||
|
"boolToUint32": {},
|
||||||
|
"uint64ToEnum": {},
|
||||||
|
"strToNested": {},
|
||||||
|
"strToOneofs": {}
|
||||||
|
}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitDefaultValues: map containing empty message",
|
||||||
|
mo: protojson.MarshalOptions{EmitDefaultValues: 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": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"strToOneofs": {
|
||||||
|
"nested": {}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitDefaultValues: oneof fields",
|
||||||
|
mo: protojson.MarshalOptions{EmitDefaultValues: true},
|
||||||
|
input: &pb3.Oneofs{},
|
||||||
|
want: `{}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitDefaultValues: extensions",
|
||||||
|
mo: protojson.MarshalOptions{EmitDefaultValues: 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: `{
|
||||||
|
"[pb2.opt_ext_nested]": {},
|
||||||
|
"[pb2.rpt_ext_nested]": [
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
}, {
|
||||||
|
desc: "EmitDefaultValues: with populated fields",
|
||||||
|
mo: protojson.MarshalOptions{EmitDefaultValues: 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: `{
|
||||||
|
"optInt32": 255,
|
||||||
|
"optUint32": 47,
|
||||||
|
"optSint32": -1001,
|
||||||
|
"optFixed32": 32,
|
||||||
|
"optSfixed32": -32,
|
||||||
|
"optFloat": 1.02,
|
||||||
|
"optBytes": "6LC35q2M"
|
||||||
}`,
|
}`,
|
||||||
}, {
|
}, {
|
||||||
desc: "UseEnumNumbers in singular field",
|
desc: "UseEnumNumbers in singular field",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user