proto: reset message by default in Unmarshal
We change Unmarshal to reset a message by default.
* We add a Merge option to UnmarshalOptions for explicit merging.
* We speed up Reset by checking for the Reset method.
* Remove TODOs in prototext and protojson about reset behavior.
Fixes golang/protobuf#890
Change-Id: Ibd8963c741053f564acf061fbdb846699942109c
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/195457
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/encoding/protojson/decode.go b/encoding/protojson/decode.go
index cb4f7fb..799dc1d 100644
--- a/encoding/protojson/decode.go
+++ b/encoding/protojson/decode.go
@@ -54,9 +54,6 @@
// setting the fields. If it returns an error, the given message may be
// partially set.
func (o UnmarshalOptions) Unmarshal(b []byte, m proto.Message) error {
- // TODO: Determine if we would like to have an option for merging or only
- // have merging behavior. We should at least be consistent with textproto
- // marshaling.
proto.Reset(m)
if o.Resolver == nil {
diff --git a/encoding/prototext/decode.go b/encoding/prototext/decode.go
index ef46a6d..2f2a0e9 100644
--- a/encoding/prototext/decode.go
+++ b/encoding/prototext/decode.go
@@ -53,10 +53,6 @@
// Unmarshal reads the given []byte and populates the given proto.Message using options in
// UnmarshalOptions object.
func (o UnmarshalOptions) Unmarshal(b []byte, m proto.Message) error {
- // Clear all fields before populating it.
- // TODO: Determine if this needs to be consistent with protojson and binary unmarshal where
- // behavior is to merge values into existing message. If decision is to not clear the fields
- // ahead, code will need to be updated properly when merging nested messages.
proto.Reset(m)
// Parse into text.Value of message type.
diff --git a/internal/impl/decode.go b/internal/impl/decode.go
index f71d19d..4441d4b 100644
--- a/internal/impl/decode.go
+++ b/internal/impl/decode.go
@@ -40,6 +40,7 @@
func (o unmarshalOptions) Options() proto.UnmarshalOptions {
return proto.UnmarshalOptions{
+ Merge: true,
AllowPartial: true,
DiscardUnknown: o.DiscardUnknown(),
Resolver: o.Resolver(),
diff --git a/proto/decode.go b/proto/decode.go
index 8974139..ab62ff3 100644
--- a/proto/decode.go
+++ b/proto/decode.go
@@ -21,6 +21,11 @@
type UnmarshalOptions struct {
pragma.NoUnkeyedLiterals
+ // Merge merges the input into the destination message.
+ // The default behavior is to always reset the message before unmarshaling,
+ // unless Merge is specified.
+ Merge bool
+
// AllowPartial accepts input for messages that will result in missing
// required fields. If AllowPartial is false (the default), Unmarshal will
// return an error if there are any missing required fields.
@@ -49,7 +54,9 @@
o.Resolver = protoregistry.GlobalTypes
}
- // TODO: Reset m?
+ if !o.Merge {
+ Reset(m)
+ }
err := o.unmarshalMessage(b, m.ProtoReflect())
if err != nil {
return err
diff --git a/proto/proto_methods.go b/proto/proto_methods.go
index 50abbd6..86dde4a 100644
--- a/proto/proto_methods.go
+++ b/proto/proto_methods.go
@@ -12,6 +12,8 @@
"google.golang.org/protobuf/runtime/protoiface"
)
+const hasProtoMethods = true
+
func protoMethods(m protoreflect.Message) *protoiface.Methods {
if x, ok := m.(protoiface.Methoder); ok {
return x.ProtoMethods()
diff --git a/proto/proto_reflect.go b/proto/proto_reflect.go
index 79ec456..b103d43 100644
--- a/proto/proto_reflect.go
+++ b/proto/proto_reflect.go
@@ -12,6 +12,8 @@
"google.golang.org/protobuf/runtime/protoiface"
)
+const hasProtoMethods = false
+
func protoMethods(m protoreflect.Message) *protoiface.Methods {
return nil
}
diff --git a/proto/reset.go b/proto/reset.go
index f14d9ae..dcf70b9 100644
--- a/proto/reset.go
+++ b/proto/reset.go
@@ -9,7 +9,10 @@
// Reset clears every field in the message.
func Reset(m Message) {
// TODO: Document memory aliasing guarantees.
- // TODO: Add fast-path for reset?
+ if mr, ok := m.(interface{ Reset() }); ok && hasProtoMethods {
+ mr.Reset()
+ return
+ }
resetMessage(m.ProtoReflect())
}
diff --git a/runtime/protoiface/methods.go b/runtime/protoiface/methods.go
index 5984a26..e0adf9d 100644
--- a/runtime/protoiface/methods.go
+++ b/runtime/protoiface/methods.go
@@ -73,6 +73,7 @@
type UnmarshalOptions struct {
pragma.NoUnkeyedLiterals
+ Merge bool // must be treated as true by method implementations
AllowPartial bool // must be treated as true by method implementations
DiscardUnknown bool
Resolver interface {