问答 询问社区的大佬们,python 基于反射的方式解析 protobuf 为 json 格式的指导

胡恒章 · 2022年04月27日 · 最后由 skottZy 回复于 2022年05月28日 · 7253 次阅读

各位大佬好,想请问一下 Python 如何基于反射的方法去实现 json 格式和 pb 文件的相互转换,

共收到 22 条回复 时间 点赞

pb 文件值的是编译过的 pb2.py 文件?还是原生的 protobuf 文件?

伍个一 回复

希望是原生的 protobuf 文件

胡恒章 回复

那无解,反射解决不了格式转换,protobuf 原生文件里可能依赖有大量的第三方依赖,例如 google 的 int64 数据格式,这些数据格式是 Python 不支持的,还是需要编译 protobuf 之后拿到 pb2.py 文件才能调用

伍个一 回复

那如何基于 编译后的 pb2.py 文件,进行 json 和 pb 序列化数据的准换呢
目前了解到的是都是需要 import ***pb2.py 然后再根据 protobuf 文件结构去序列化和反序列化。 但是这种方式因为在第一行要导入模块所以感觉不太通用。是想做一个通用的方法去解析多个 protobuf 文件然后在官网上了解到 reflection 模块中 ParseMessage 方法

.protobuf.reflection.ParseMessage(descriptor, byte_str)

但这个方法第一个参数对象的初始化里面很多参数都不太清楚

class google.protobuf.descriptor.Descriptor(name, full_name, filename, containing_type, fields, nested_types, enum_types, extensions, options=None, serialized_options=None, is_extendable=True, extension_ranges=None, oneofs=None, file=None, serialized_start=None, serialized_end=None, syntax=None, create_key=None)

可以看下官方文档,google.protobuf.json_format 可以解决你遇到的问题,ParseDict(buffer, msg), MessageToDict(msg)

skottZy 回复

非常感谢解答,但是还是有几点疑惑哈第一个是 self.descriptor.FindFileByName(file_name)¶ 这个方法传参应该是一个文件名字吧,返回的是一个 FileDescriptor 对象。但是 GetMessages() 传参对象是一个 file_protos – Iterable of FileDescriptorProto 是不能直接传递的吧

skottZy 回复

另外一个就是 self.pbMoudle 这个列表传值是传生成的 pb 模块名字吗

胡恒章 回复

是的

胡恒章 回复

构造方法里面调用 addModule,会把你的 pb 对象都加载到内存,有两个方法写错了,外网没有 pb,不能 debug,python 的 ide 不是强类型检查,自动补全没仔细看,所以写错了,不好意思哈,代码看下面

skottZy 回复

我参照你最新的调试了一下,这个 msgName 应该传承的是 proto 文件中 message 对象的名字吧

胡恒章 回复

FindMessageTypeByName(msgName)

skottZy 回复

这个调试可以了,非常感谢指导👍

😧 我理解你是否想把 python 的 dict 直接转成 pb2.py 文件中的具体的信息类?然后自动填充对应的字段?希望对你有帮助

import simplejson
from google.protobuf.descriptor import FieldDescriptor as FD

class ConvertException(Exception):
    pass

def dict2pb(cls, adict, strict=False):
    obj = cls()
    for field in obj.DESCRIPTOR.fields:
        if not field.label == field.LABEL_REQUIRED:
            continue
        if not field.has_default_value:
            continue
        if not field.name in adict:
            raise ConvertException('Field "%s" missing from descriptor dictionary.'
                                   % field.name)
    field_names = set([field.name for field in obj.DESCRIPTOR.fields])
    if strict:
        for key in adict.keys():
            if key not in field_names:
                raise ConvertException(
                    'Key "%s" can not be mapped to field in %s class.'
                    % (key, type(obj)))
    for field in obj.DESCRIPTOR.fields:
        if not field.name in adict:
            continue
        msg_type = field.message_type
        if field.label == FD.LABEL_REPEATED:
            if field.type == FD.TYPE_MESSAGE:
                for sub_dict in adict[field.name]:
                    item = getattr(obj, field.name).add()
                    item.CopyFrom(dict2pb(msg_type._concrete_class, sub_dict))
            else:
                map(getattr(obj, field.name).append, adict[field.name])
        else:
            if field.type == FD.TYPE_MESSAGE:
                value = dict2pb(msg_type._concrete_class, adict[field.name])
                getattr(obj, field.name).CopyFrom(value)
            else:
                setattr(obj, field.name, adict[field.name])
    return obj
胡恒章 回复

😂 python 用的少了,现在都用 c++

向死而生 回复

google 已经提供了接口去做这件事情

skottZy 回复

好像实现还是有点问题 ,这样返回的 message 对象好像不对,导致用 json 转 pb 的时候转出的结果是空的

胡恒章 回复

instance 回来的是 bool 值哦,create 的时候需要引入 proto 的类型,createPB("log_pb2.logTag")

skottZy 回复

那就是函数 createPb 中 msg name 我传错了吗

skottZy 回复

弱弱问一句,大佬在广州做游戏的么

胡恒章 回复

确认一下文件是不是最新,看堆栈就是找不到了,可以 debug 调试一下看看为什么找不到

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册