/// <summary> /// Merges all fields specified by this FieldMaskTree from <paramref name="source"/> to <paramref name="destination"/>. /// </summary> public void Merge(IMessage source, IMessage destination, FieldMask.MergeOptions options) { if (source.Descriptor != destination.Descriptor) { throw new InvalidProtocolBufferException("Cannot merge messages of different types."); } if (root.Children.Count == 0) { return; } Merge(root, "", source, destination, options); }
public void Merge(bool useDynamicMessage) { TestAllTypes value = new TestAllTypes { SingleInt32 = 1234, SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 5678 }, RepeatedInt32 = { 4321 }, RepeatedNestedMessage = { new TestAllTypes.Types.NestedMessage { Bb = 8765 } } }; NestedTestAllTypes source = new NestedTestAllTypes { Payload = value, Child = new NestedTestAllTypes { Payload = value } }; // Now we have a message source with the following structure: // [root] -+- payload -+- single_int32 // | +- single_nested_message // | +- repeated_int32 // | +- repeated_nested_message // | // +- child --- payload -+- single_int32 // +- single_nested_message // +- repeated_int32 // +- repeated_nested_message FieldMask.MergeOptions options = new FieldMask.MergeOptions(); // Test merging each individual field. NestedTestAllTypes destination = new NestedTestAllTypes(); Merge(new FieldMaskTree().AddFieldPath("payload.single_int32"), source, destination, options, useDynamicMessage); NestedTestAllTypes expected = new NestedTestAllTypes { Payload = new TestAllTypes { SingleInt32 = 1234 } }; Assert.AreEqual(expected, destination); destination = new NestedTestAllTypes(); Merge(new FieldMaskTree().AddFieldPath("payload.single_nested_message"), source, destination, options, useDynamicMessage); expected = new NestedTestAllTypes { Payload = new TestAllTypes { SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 5678 } } }; Assert.AreEqual(expected, destination); destination = new NestedTestAllTypes(); Merge(new FieldMaskTree().AddFieldPath("payload.repeated_int32"), source, destination, options, useDynamicMessage); expected = new NestedTestAllTypes { Payload = new TestAllTypes { RepeatedInt32 = { 4321 } } }; Assert.AreEqual(expected, destination); destination = new NestedTestAllTypes(); Merge(new FieldMaskTree().AddFieldPath("payload.repeated_nested_message"), source, destination, options, useDynamicMessage); expected = new NestedTestAllTypes { Payload = new TestAllTypes { RepeatedNestedMessage = { new TestAllTypes.Types.NestedMessage { Bb = 8765 } } } }; Assert.AreEqual(expected, destination); destination = new NestedTestAllTypes(); Merge( new FieldMaskTree().AddFieldPath("child.payload.single_int32"), source, destination, options, useDynamicMessage); expected = new NestedTestAllTypes { Child = new NestedTestAllTypes { Payload = new TestAllTypes { SingleInt32 = 1234 } } }; Assert.AreEqual(expected, destination); destination = new NestedTestAllTypes(); Merge( new FieldMaskTree().AddFieldPath("child.payload.single_nested_message"), source, destination, options, useDynamicMessage); expected = new NestedTestAllTypes { Child = new NestedTestAllTypes { Payload = new TestAllTypes { SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 5678 } } } }; Assert.AreEqual(expected, destination); destination = new NestedTestAllTypes(); Merge(new FieldMaskTree().AddFieldPath("child.payload.repeated_int32"), source, destination, options, useDynamicMessage); expected = new NestedTestAllTypes { Child = new NestedTestAllTypes { Payload = new TestAllTypes { RepeatedInt32 = { 4321 } } } }; Assert.AreEqual(expected, destination); destination = new NestedTestAllTypes(); Merge(new FieldMaskTree().AddFieldPath("child.payload.repeated_nested_message"), source, destination, options, useDynamicMessage); expected = new NestedTestAllTypes { Child = new NestedTestAllTypes { Payload = new TestAllTypes { RepeatedNestedMessage = { new TestAllTypes.Types.NestedMessage { Bb = 8765 } } } } }; Assert.AreEqual(expected, destination); destination = new NestedTestAllTypes(); Merge(new FieldMaskTree().AddFieldPath("child").AddFieldPath("payload"), source, destination, options, useDynamicMessage); Assert.AreEqual(source, destination); // Test repeated options. destination = new NestedTestAllTypes { Payload = new TestAllTypes { RepeatedInt32 = { 1000 } } }; Merge(new FieldMaskTree().AddFieldPath("payload.repeated_int32"), source, destination, options, useDynamicMessage); // Default behavior is to append repeated fields. Assert.AreEqual(2, destination.Payload.RepeatedInt32.Count); Assert.AreEqual(1000, destination.Payload.RepeatedInt32[0]); Assert.AreEqual(4321, destination.Payload.RepeatedInt32[1]); // Change to replace repeated fields. options.ReplaceRepeatedFields = true; Merge(new FieldMaskTree().AddFieldPath("payload.repeated_int32"), source, destination, options, useDynamicMessage); Assert.AreEqual(1, destination.Payload.RepeatedInt32.Count); Assert.AreEqual(4321, destination.Payload.RepeatedInt32[0]); // Test message options. destination = new NestedTestAllTypes { Payload = new TestAllTypes { SingleInt32 = 1000, SingleUint32 = 2000 } }; Merge(new FieldMaskTree().AddFieldPath("payload"), source, destination, options, useDynamicMessage); // Default behavior is to merge message fields. Assert.AreEqual(1234, destination.Payload.SingleInt32); Assert.AreEqual(2000, destination.Payload.SingleUint32); // Test merging unset message fields. NestedTestAllTypes clearedSource = source.Clone(); clearedSource.Payload = null; destination = new NestedTestAllTypes(); Merge(new FieldMaskTree().AddFieldPath("payload"), clearedSource, destination, options, useDynamicMessage); Assert.IsNull(destination.Payload); // Skip a message field if they are unset in both source and target. destination = new NestedTestAllTypes(); Merge(new FieldMaskTree().AddFieldPath("payload.single_int32"), clearedSource, destination, options, useDynamicMessage); Assert.IsNull(destination.Payload); // Change to replace message fields. options.ReplaceMessageFields = true; destination = new NestedTestAllTypes { Payload = new TestAllTypes { SingleInt32 = 1000, SingleUint32 = 2000 } }; Merge(new FieldMaskTree().AddFieldPath("payload"), source, destination, options, useDynamicMessage); Assert.AreEqual(1234, destination.Payload.SingleInt32); Assert.AreEqual(0, destination.Payload.SingleUint32); // Test merging unset message fields. destination = new NestedTestAllTypes { Payload = new TestAllTypes { SingleInt32 = 1000, SingleUint32 = 2000 } }; Merge(new FieldMaskTree().AddFieldPath("payload"), clearedSource, destination, options, useDynamicMessage); Assert.IsNull(destination.Payload); // Test merging unset primitive fields. destination = source.Clone(); destination.Payload.SingleInt32 = 0; NestedTestAllTypes sourceWithPayloadInt32Unset = destination; destination = source.Clone(); Merge(new FieldMaskTree().AddFieldPath("payload.single_int32"), sourceWithPayloadInt32Unset, destination, options, useDynamicMessage); Assert.AreEqual(0, destination.Payload.SingleInt32); // Change to clear unset primitive fields. options.ReplacePrimitiveFields = true; destination = source.Clone(); Merge(new FieldMaskTree().AddFieldPath("payload.single_int32"), sourceWithPayloadInt32Unset, destination, options, useDynamicMessage); Assert.IsNotNull(destination.Payload); }
private void Merge(FieldMaskTree tree, IMessage source, IMessage destination, FieldMask.MergeOptions options, bool useDynamicMessage) { if (useDynamicMessage) { var newSource = source.Descriptor.Parser.CreateTemplate(); newSource.MergeFrom(source.ToByteString()); var newDestination = source.Descriptor.Parser.CreateTemplate(); newDestination.MergeFrom(destination.ToByteString()); tree.Merge(newSource, newDestination, options); // Clear before merging: foreach (var fieldDescriptor in destination.Descriptor.Fields.InFieldNumberOrder()) { fieldDescriptor.Accessor.Clear(destination); } destination.MergeFrom(newDestination.ToByteString()); } else { tree.Merge(source, destination, options); } }
/// <summary> /// Merges all fields specified by a sub-tree from <paramref name="source"/> to <paramref name="destination"/>. /// </summary> private void Merge( Node node, string path, IMessage source, IMessage destination, FieldMask.MergeOptions options) { if (source.Descriptor != destination.Descriptor) { throw new InvalidProtocolBufferException($"source ({source.Descriptor}) and destination ({destination.Descriptor}) descriptor must be equal"); } var descriptor = source.Descriptor; foreach (var entry in node.Children) { var field = descriptor.FindFieldByName(entry.Key); if (field == null) { Debug.WriteLine($"Cannot find field \"{entry.Key}\" in message type \"{descriptor.FullName}\""); continue; } if (entry.Value.Children.Count != 0) { if (field.IsRepeated || field.FieldType != FieldType.Message) { Debug.WriteLine($"Field \"{field.FullName}\" is not a singular message field and cannot have sub-fields."); continue; } var sourceField = field.Accessor.GetValue(source); var destinationField = field.Accessor.GetValue(destination); if (sourceField == null && destinationField == null) { // If the message field is not present in both source and destination, skip recursing // so we don't create unnecessary empty messages. continue; } if (destinationField == null) { // If we have to merge but the destination does not contain the field, create it. destinationField = field.MessageType.Parser.CreateTemplate(); field.Accessor.SetValue(destination, destinationField); } var childPath = path.Length == 0 ? entry.Key : path + "." + entry.Key; Merge(entry.Value, childPath, (IMessage)sourceField, (IMessage)destinationField, options); continue; } if (field.IsRepeated) { if (options.ReplaceRepeatedFields) { field.Accessor.Clear(destination); } var sourceField = (IList)field.Accessor.GetValue(source); var destinationField = (IList)field.Accessor.GetValue(destination); foreach (var element in sourceField) { destinationField.Add(element); } } else { var sourceField = field.Accessor.GetValue(source); if (field.FieldType == FieldType.Message) { if (options.ReplaceMessageFields) { if (sourceField == null) { field.Accessor.Clear(destination); } else { field.Accessor.SetValue(destination, sourceField); } } else { if (sourceField != null) { // Well-known wrapper types are represented as nullable primitive types, so we do not "merge" them. // Instead, any non-null value just overwrites the previous value directly. if (field.MessageType.IsWrapperType) { field.Accessor.SetValue(destination, sourceField); } else { var sourceByteString = ((IMessage)sourceField).ToByteString(); var destinationValue = (IMessage)field.Accessor.GetValue(destination); if (destinationValue != null) { destinationValue.MergeFrom(sourceByteString); } else { field.Accessor.SetValue(destination, field.MessageType.Parser.ParseFrom(sourceByteString)); } } } } } else { if (sourceField != null || !options.ReplacePrimitiveFields) { field.Accessor.SetValue(destination, sourceField); } else { field.Accessor.Clear(destination); } } } } }