diff --git a/csharp/src/Google.Protobuf.Test/FieldMaskTreeTest.cs b/csharp/src/Google.Protobuf.Test/FieldMaskTreeTest.cs index 380ef72d37..d477cb35b1 100644 --- a/csharp/src/Google.Protobuf.Test/FieldMaskTreeTest.cs +++ b/csharp/src/Google.Protobuf.Test/FieldMaskTreeTest.cs @@ -406,6 +406,12 @@ namespace Google.Protobuf Merge(new FieldMaskTree().AddFieldPath("payload.single_int32"), sourceWithPayloadInt32Unset, destination, options, useDynamicMessage); Assert.IsNotNull(destination.Payload); + + // Clear unset primitive fields even if source payload is cleared + destination = source.Clone(); + Merge(new FieldMaskTree().AddFieldPath("payload.single_int32"), + clearedSource, destination, options, useDynamicMessage); + Assert.AreEqual(0, destination.Payload.SingleInt32); } [Test] diff --git a/csharp/src/Google.Protobuf/FieldMaskTree.cs b/csharp/src/Google.Protobuf/FieldMaskTree.cs index aaa780ba92..b8a8fff586 100644 --- a/csharp/src/Google.Protobuf/FieldMaskTree.cs +++ b/csharp/src/Google.Protobuf/FieldMaskTree.cs @@ -270,6 +270,13 @@ namespace Google.Protobuf field.Accessor.SetValue(destination, destinationField); } + if (sourceField == null) + { + // If the message field is not present in the source but is in the destination, create an empty one + // so we can properly handle child entries + sourceField = field.MessageType.Parser.CreateTemplate(); + } + var childPath = path.Length == 0 ? entry.Key : path + "." + entry.Key; Merge(entry.Value, childPath, (IMessage)sourceField, (IMessage)destinationField, options); continue;