mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-03-24 04:43:30 +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
|
||||
|
||||
// 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
|
||||
// messages. If nil, this defaults to using protoregistry.GlobalTypes.
|
||||
Resolver interface {
|
||||
@ -178,7 +197,11 @@ func (m typeURLFieldRanger) Range(f func(protoreflect.FieldDescriptor, protorefl
|
||||
|
||||
// unpopulatedFieldRanger wraps a protoreflect.Message and modifies its Range
|
||||
// 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) {
|
||||
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()
|
||||
isSingularMessage := fd.Cardinality() != protoreflect.Repeated && fd.Message() != nil
|
||||
if isProto2Scalar || isSingularMessage {
|
||||
if m.skipNull {
|
||||
continue
|
||||
}
|
||||
v = protoreflect.Value{} // use invalid value to emit null
|
||||
}
|
||||
if !f(fd, v) {
|
||||
@ -217,8 +243,11 @@ func (e encoder) marshalMessage(m protoreflect.Message, typeURL string) error {
|
||||
defer e.EndObject()
|
||||
|
||||
var fields order.FieldRanger = m
|
||||
if e.opts.EmitUnpopulated {
|
||||
fields = unpopulatedFieldRanger{m}
|
||||
switch {
|
||||
case e.opts.EmitUnpopulated:
|
||||
fields = unpopulatedFieldRanger{Message: m, skipNull: false}
|
||||
case e.opts.EmitDefaultValues:
|
||||
fields = unpopulatedFieldRanger{Message: m, skipNull: true}
|
||||
}
|
||||
if typeURL != "" {
|
||||
fields = typeURLFieldRanger{fields, typeURL}
|
||||
|
@ -2192,6 +2192,222 @@ func TestMarshal(t *testing.T) {
|
||||
"optDouble": null,
|
||||
"optBytes": "6LC35q2M",
|
||||
"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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user