覆盖 protobuf MergeFrom 与 MergeFromString 中的重复字段

Overwriting a repeated field in protobuf MergeFrom vs. MergeFromString

假设我有一个原型定义为:

MyProto {
    optional MyWrapper wrapper = 1;
}

其中:

MyWrapper {
    repeated int32 data = 1;
}

当我在 MyProto 的两个文本规范上调用 MergeFromString 时,包装器内重复字段的两个版本被连接起来(一个附加到另一个。)我真的希望它们而是被覆盖。 MergeFromString 的文档说:

When we find a field in |serialized| that is already present in this message:

  • If it's a "repeated" field, we append to the end of our list.
  • Else, if it's a scalar, we overwrite our field.
  • Else, (it's a nonrepeated composite), we recursively merge into the existing composite.

显然,对于包装器,我们正在谈论第三种情况。所以我们递归地合并,在下一次循环中,我们看到一个重复的字段,并且值被附加到目标。所以我明白为什么会这样了。

将此与 MergeFrom 的规格进行比较:

This method merges the contents of the specified message into the current message. Singular fields that are set in the specified message overwrite the corresponding fields in the current message. Repeated fields are appended. Singular sub-messages and groups are recursively merged.

那么wrapper字段不就是一个单一的字段吗,wrapper不会被重写吗?

所以我的问题有两个方面,

1) 这是否不一致,还是我误解了什么?

2) 当我调用 MergeFromString 时,如何获得我想要的覆盖而不是合并重复字段的行为?

对于问题的第一部分,您引用的 MergeFrom 规范在技术上是正确的,尽管措辞有点混乱。它说 "singular fields" 被覆盖但是 "singular sub-messages" 被递归合并,你的包装器将被认为是一个单一的子 - 留言。

要获得您想要的行为,您应该能够使用 FieldMaskUtil。特别是您可以调用 FieldMaskUtil::MergeMessageTo(...) 并传递一个 MergeOptions 配置为替换重复字段而不是连接它们。为此,您首先必须从文本格式表示中解析这两条消息。

我实施了 StrictMerge 来解决这个问题。解决方案很简单,您只需从目标原型中清除任何不需要的重复字段。但是,我们只有在看到与源消息中的相应字段冲突时才清除。

void StrictMerge(const Message& source, Message* target) {
  ClearRepeatedField(source, target);
  target->MergeFrom(source);
}

ClearRepeatedField 递归迭代源和目标。在每个级别,如果源中存在冲突的重复字段,它只会清除目标中的重复字段。

void ClearRepeatedField(const Message& source, Message* target) {
  const Descriptor* source_descriptor = source.GetDescriptor();
  const Reflection* source_reflection = source.GetReflection();
  const Descriptor* target_descriptor = target->GetDescriptor();
  const Reflection* target_reflection = target->GetReflection();
  for (int i = 0; i < source_descriptor->field_count(); i++) {
    const FieldDescriptor* source_field = source_descriptor->field(I);
    const FieldDescriptor* target_field = target_descriptor->field(I);
    if (source_field->is_map()) {
      // Do nothing for a map
      continue;
    }
    if (source_field->is_repeated()) {
      // Clear only if source field is not empty
      if (source_reflection->FieldSize(source, source_field) > 0) {
        target_reflection->ClearField(target, target_field);
      }
      continue;
    }
    bool has_message_value =
      source_field->type() == FieldDescriptor::TYPE_MESSAGE &&
      source_reflection->HasField(source, source_field);
      if (has_message_value) {
        const Message& source_message = source_reflection->GetMessage(
          source, source_field);
        Message* target_message = target_reflection->MutableMessage(
        target, target_field);
        // Drop repeated fields from this field's target
        ClearRepeatedField(source_message, target_message);
      }
    }
}