谷歌协议缓冲区比较

40

我想比较两个消息或(两个子参数)的谷歌协议缓冲区。 我找不到可以实现这一点的API。

有什么想法吗?


你能具体说明你想要比较什么吗?是消息结构、消息字段值还是两者同时比较? - abyss.7
6个回答

49
您可以使用类google::protobuf::util::MessageDifferencer来实现此功能。我认为它自v3.0.2版本以来才可用:

在google/protobuf/util目录中引入了新的实用程序函数/类:

  • MessageDifferencer:比较两个proto消息并报告它们的差异。
#include <google/protobuf/util/message_differencer.h>

MessageDifferencer::Equals(msg1, msg2);

4
补充说明:MessageDifferencer仅适用于C++。 - deddebme
3
根据您的消息类型字段是否可以具有默认值,您可能需要考虑使用MessageDifferencer :: Equivalent而不是Equals - pestophagous
确实,我有一个单元测试失败了,因为它比较了一个复杂消息结构的二进制和字符串变体,但是MessageDifferencer修复了它! - Robert
1
请注意,如果您使用的是库的精简版,则此方法无法正常工作,因为 MessageDifferencer::Equals 要求参数为 Message 而非 MessageLiteMessage 的派生类)。Equals 需要 GetDescriptor() 函数,而此函数仅在 Message 中实现。因此,在这种情况下,必须选择其他答案中提到的序列化方法,并了解其局限性。 - Antonio

14
你可以相信一个事实,那就是你所有的protobuf消息都继承自google::protobuf::MessageLite类型,该类型具有比较任何两个protobuf消息所需的一切,无论它们是否属于相同的派生类型:
bool operator==(const google::protobuf::MessageLite& msg_a,
                const google::protobuf::MessageLite& msg_b) {
  return (msg_a.GetTypeName() == msg_b.GetTypeName()) &&
      (msg_a.SerializeAsString() == msg_b.SerializeAsString());
}

编辑

正如在下面的评论中指出的那样,特别是对于map字段,此答案是不正确的。map元素具有不确定的排序。如果您的消息中可能包含map字段,请使用MessageDifferencer


1
哇,这应该是对这个用户问题的完美回答!太棒了! - Peaches491
1
这种方法比 MessageDifferencer::Equals(msg1, msg2); 更快。 - user875367
6
根据文档:
对于一个名为 foo 的协议缓冲消息实例,以下检查可能会失败:foo.SerializeAsString() == foo.SerializeAsString() 这是由于编码消息时字段顺序的非确定性引起的: https://developers.google.com/protocol-buffers/docs/encoding#order
- pooya13
在实践中,我发现上述评论对于map字段尤其正确。如果您的protobuf消息可能具有map字段,则SerializeAsString()将无法以非确定性方式失败。 - Jake Askeland

9

您可以使用 message.DebugString,或者您也可以执行以下操作:

std::string strMsg;
message.SerializeToString(&strMsg);

使用两个(二进制)字符串来比较消息,然后进行比较。我没有测试性能,但我认为它比通过.DebugString()返回的可读的消息字符串更快。+您可以使用protobuf-lite库进行此操作(而对于message.DebugString,您需要完整版本)。


7
序列化结果不能保证在不同二进制文件和时间上始终一致。例如,对于一个二进制文件而言未知的扩展将被序列化为最后一个(作为未知字段),已知的扩展之后。因此,序列化结果将取决于序列化时可用的描述符。因此,比较序列化的消息是检查相等性的错误方式。使用MessageDifferencer是正确的方法。 - Remy Blank

1

协议缓冲区只是某些对象类型的序列化格式。为什么不使用协议缓冲区重建原始对象,然后允许这些对象使用类中构建的任何比较逻辑进行比较呢?


这是我的理解。根据此文档:https://developers.google.com/protocol-buffers/docs/overview#not-good-fit,“当协议缓冲区被序列化时,相同的数据可以有许多不同的二进制序列化。您无法在完全解析它们之前比较两个消息是否相等。” - netskink

-1

这可能不是最理想的解决方案,但我认为可以通过以下方式实现:

messageA.DebugString() == messageB.DebugString();

除此之外,我认为唯一的解决方案就是创建自己的Message子类并实现一个bool operator==(const Message&)


@idimba 谢谢。但我想你应该编辑你的问题并提到速度问题;-)。但是,我猜,唯一的真正选择就是自己实现比较运算符。 - Gianni
3
正如 Protobuf 文档所说,继承自 Message 不是一个好的选择。我同意这一点,因为你将不得不更改由 protoc 生成的文件,因此更改原始 proto 将会很困难,因为你将不得不在每次编译时都进行更改。 - Alexander Shishenko

-6
您可以比较描述符的指针(超快速):
if (mMessages[i]->body()->GetDescriptor() == T::descriptor())

mMessages是一个带有头部和加密的网络消息池,它使用protobuf主体(google::protobuf::Message*)创建数据包。

因此,为了获得正确类型的消息,我将比较描述符常量指针,该指针对于每种类型的消息都相同(不是100%确定,但到目前为止我没有遇到任何问题)。

这将是比较protobuf消息最快的方法,而无需使用字符串比较,顺便说一句,您可以从描述符中获取类型名称。:-)


喜欢我的答案被投票否决,但这确实是比较消息的正确方法。 - KukoBits
13
如果你想比较类型,那么这是正确的。但这个问题似乎是关于比较内容,而这显然不是在进行比较。这就是为什么你会收到负评的原因,我敢肯定。 - Dark Falcon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接