为什么要这么做?
-
类型安全
方法开始时检查输入是否为 protobuf Message 类型,防止错误输入 检查字段是否存在,避免处理不完整的数据 -
全面处理各种字段类型
普通标量字段: 直接转换 枚举字段: 将数字值转换为可读的字符串 repeated 字段: 正确处理列表类型 嵌套消息: 递归处理 map 字段: 保持键值对结构 -
可读性增强
枚举值转换为字符串形式,更易读 嵌套结构展开为 Python 原生字典,便于理解 -
兼容性
处理了可能出现的异常情况(如无效枚举值) 保留了原始值作为后备,不会因为转换失败而丢失数据 -
一致性
无论 protobuf 消息结构如何复杂,都能转换为统一的字典格式 便于后续的 JSON 序列化或其它处理
目录结构

文件拆解
example.proto
syntax = "proto3";
package example;
enum CrossRewardItemStatus {
CRIS_None = 0; // 不可领取状态
CRIS_NotClaimed = 1; // 可领取状态
CRIS_Claimed = 2; // 已领取状态
}
message ActivityCross {
repeated CrossRewardItemStatus items = 1; // 奖励状态
}
protobuf2dict/members.py
from google.protobuf import __version__
from packaging.version import Version
from google.protobuf.internal import api_implementation
from google.protobuf.internal.containers import *
from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper
from google.protobuf.message import Message
pb_version = Version(__version__)
major = pb_version.major
minor = pb_version.minor
micro = pb_version.micro
if api_implementation.Type() == 'cpp':
raise RuntimeError("需要运行check脚本更换兼容arm架构的protobuf")
else:
from google.protobuf.descriptor import EnumValueDescriptor, EnumDescriptor
types_enum = (
EnumValueDescriptor,
EnumTypeWrapper,
EnumDescriptor,
)
types_list = (
RepeatedScalarFieldContainer,
RepeatedCompositeFieldContainer,
BaseContainer
)
types_dict = (
MessageMap,
ScalarMap,
)
__all__ = ['Message',
'types_dict', 'types_list', 'types_enum',
'major', 'minor', 'micro',
]
protobuf2dict/pb2dict.py
from .members import types_dict, types_enum, types_list, Message, EnumValueDescriptor
from google.protobuf import descriptor as _descriptor
class ProtobufToDict:
__slots__ = ()
@classmethod
def pb_to_dict_new(cls, pb):
if not isinstance(pb, Message):
raise TypeError("pb must be protobuf Message type")
result_dict = {}
descriptor = pb.DESCRIPTOR
for field in descriptor.fields:
field_name = field.name
field_value = getattr(pb, field_name, None)
if field_value is None:
raise RuntimeError(f"未在{pb.__class__.__name__}中找到字段{field_name}")
if isinstance(field_value, Message) and not pb.HasField(field_name):
# if not pb.HasField(field_name):
result_dict[field.name] = None
continue
# 处理 repeated 字段
if field.label == _descriptor.FieldDescriptor.LABEL_REPEATED:
result_dict[field.name] = cls._handle_list(field_value)
continue
# 处理枚举字段
if field.type == _descriptor.FieldDescriptor.TYPE_ENUM:
if isinstance(field_value, list): # 防止重复字段被错误处理为枚举
result_dict[field.name] = field_value
continue
field_value = field.enum_type.values_by_number[field_value]
result_dict[field.name] = cls._handle_type(field_value)
continue
# 处理普通字段
result_dict[field.name] = cls._handle_type(field_value)
return result_dict
@classmethod
def _handle_type(cls, value):
if isinstance(value, types_list):
return cls._handle_list(value)
elif isinstance(value, types_dict):
return cls._handle_dict(value)
elif isinstance(value, types_enum):
return cls._handle_enum(value)
elif isinstance(value, Message):
return cls.pb_to_dict(value)
else:
return value
@classmethod
def _handle_list(cls, field_value):
result = []
for value in field_value:
result.append(cls._handle_type(value))
return result
@classmethod
def _handle_dict(cls, field_value):
result_dict = {}
for key, value in field_value.items():
result_dict[key] = cls._handle_type(value)
return result_dict
@classmethod
def _handle_enum(cls, field_value):
result_dict = {}
if hasattr(field_value, '__members__'):
for key in getattr(field_value, "__members__"):
value = getattr(field_value, key).value
result_dict[key] = cls._handle_type(value)
elif isinstance(field_value, EnumValueDescriptor):
result_dict[field_value.name] = cls._handle_type(field_value.number)
else:
raise TypeError(f"Unsupported enum type: {type(field_value)}")
return result_dict
pb2dict = ProtobufToDict.pb_to_dict
protobuf2dict/__init__.py
from .pb2dict import ProtobufToDict, pb2dict
requirements.txt(依赖文件,通过pip install -r requirement.txt安装)
packaging @ file:///C:/b/abs_3by6s2fa66/croot/packaging_1734472138782/work
protobuf==4.25.3
main.py(主程序入口)
from generated.example_pb2 import ActivityCross, CrossRewardItemStatus
from protobuf2dict import pb2dict
if __name__ == '__main__':
activity = ActivityCross()
activity.items.append(CrossRewardItemStatus.CRIS_None)
activity.items.append(CrossRewardItemStatus.CRIS_None)
activity.items.append(CrossRewardItemStatus.CRIS_None)
activity.items.append(CrossRewardItemStatus.CRIS_None)
activity.items.append(CrossRewardItemStatus.CRIS_None)
x = pb2dict(activity)
print(x)
注意事项
对于example.proto中items的类型为枚举类型的数组(既被定义为enum又被定义为repeated)
在pb2dict方法中需要先判断字段是否是repeated类型再去判断enum类型
如果只有对enum类型的判断,那么这里的字段items你会发现也会被匹配上
但是item的值是数组(列表),无法被作为key来查找值的

因此也经常由于类型判断缺失导致repeated被误认为enum而报错

792

被折叠的 条评论
为什么被折叠?



