首页 > 解决方案 > 如何在 Protobuf 中序列化嵌套消息中的默认值

问题描述

正如标题所述,我有一条 protobuf 消息,其中包含另一条消息,如下所示:

syntax = "proto3";

message Message
{
    message SubMessage {
        int32 number = 1;
    }
    
    SubMessage subMessage = 1;
}

example.json是空的(这意味着到处都是默认值):

{
}

在我的 python 脚本中,我阅读了这条消息:

example_json = open("example.json", "r").read()

example_message = example.Message()
google.protobuf.json_format.Parse(example_json, example_message)

当我检查example_message.subMessage.number它的值0是正确的。

现在我想将它转换为所有值都存在的字典 - 甚至是默认值。对于转换,我使用方法google.protobuf.json_format.MessageToDict()。但是您可能知道MessageToDict(),如果没有我告诉它,它不会序列化默认值(就像在这个问题中一样:Protobuf doesn't serialize default values)。所以我将参数添加including_default_value_fields=True到调用中MessageToDict()

protobuf.MessageToDict(example_message, including_default_value_fields=True)

返回:

{}

而不是我所期望的:

{'subMessage': {'number': 0}}

protobuf 代码中的注释(可在此处找到:https ://github.com/protocolbuffers/protobuf/blob/master/python/google/protobuf/json_format.py )证实了这种行为:

include_default_value_fields:如果为 True,则将始终序列化奇异原始字段、重复字段和映射字段。如果为 False,则仅序列化非空字段。单个消息字段和 oneof 字段不受此选项影响。

那么,即使它们是嵌套消息中的默认值,我该怎么做才能获得包含所有值的 dict?


有趣的是,当我example.json看起来像这样时:

{
    "subMessage" : {
        "number" : 0
    }
}

我得到了预期的输出。但我不能确保example.json将所有值都写出来,所以这不是一个选项。

标签: pythonjsondictionaryprotocol-buffersprotobuf-3

解决方案


基于Looping over Protocol Buffers attributes in Python我创建了一个自定义MessageToDict函数:

def MessageToDict(message):
    message_dict = {}
    
    for descriptor in message.DESCRIPTOR.fields:
        key = descriptor.name
        value = getattr(message, descriptor.name)
        
        if descriptor.label == descriptor.LABEL_REPEATED:
            message_list = []
            
            for sub_message in value:
                if descriptor.type == descriptor.TYPE_MESSAGE:
                    message_list.append(MessageToDict(sub_message))
                else:
                    message_list.append(sub_message)
            
            message_dict[key] = message_list
        else:
            if descriptor.type == descriptor.TYPE_MESSAGE:
                message_dict[key] = MessageToDict(value)
            else:
                message_dict[key] = value
    
    return message_dict

鉴于从空读取的消息,example.json此函数返回:

{'subMessage': {'number': 0}}

推荐阅读