Skip to content

protobuf-to-dict 遷移到 protobuf3-to-dict ​

簡介 ​

protobuf_to_dict 和 protobuf3_to_dict 提供了 protobuf instance 和 dict 之間的轉換函數。

在有些服務裡,新的版本的 protocol 會用到新的 protobuf 的東西,例如:google.protobuf.Value, 這時,protobuf_to_dict 會壞掉,所以會需要遷移到 protobuf3_to_dict,但為了能讓舊的資料(protobuf_to_dict 生的)和新的 protobuf3_to_dict 相容,就需要做一些準備。

這篇不會提到 official protobuf python lib 的 json_format,因為的輸入輸出格式和 *protobuf_to_dict*不相容,所以並沒有考慮遷移到 json_format。

protobuf_to_dict 無法運行的地方 ​

google.protobuf.Value 裡面有一種欄位的類別是 Struct

message Struct {
  // Unordered map of dynamically typed values.
  map<string, Value> fields = 1;
}

protobuf_to_dict ​

處理 Struct 時會直接丟 Exception

python
from google.protobuf.struct_pb2 import Struct
from protobuf_to_dict import protobuf_to_dict

s = Struct()
s.update({"key": "value"})
obj = protobuf_to_dict(s)

# AttributeError` : 'unicode' object has no attribute 'ListFields'

protobuf3_to_dict ​

map 在 google 的定義上為了能夠向後相容,要求實作要接受一個 Repeated List (reference)。這在 protobuf3_to_dict 裡面做了特化處理,輸出的 json 會直接是 dict ,而不像一般 Repeated List 會輸出成 sequence。

python
if field.message_type and field.message_type.has_options and field.message_type.GetOptions().map_entry:
    result_dict[field.name] = dict()
    value_field = field.message_type.fields_by_name['value']
    type_callable = _get_field_value_adaptor(
        pb, value_field, type_callable_map,
        use_enum_labels, including_default_value_fields,
        lowercase_enum_lables)
    for k, v in value.items():
        result_dict[field.name][k] = type_callable(v)
    continue

protobuf_to_dict 和 protobuf3_to_dict 不同的地方: Bytes 欄位 ​

protobuf_to_dict 會先把 bytes 欄位 encode 成 base64 ,然後再進行處理, protobuf3_to_dict 則不會。

這造成 protobuf_to_dict 要把 dict 轉回 protobuf 時,會需要對 bytes 欄位進行 base64 decode。

假如這時要用 protobuf3_to_dic.dict_to_protobuf 套用到舊資料,或者讓 protobuf3_to_dict.protobuf_to_dict 生成的新資料和舊資料形式一樣,就會需要多傳一個 map 進去。

python
# 舊版本用到的轉換函數

TYPE_CALLABLE_MAP = {
    FieldDescriptor.TYPE_DOUBLE: float,
    FieldDescriptor.TYPE_FLOAT: float,
    FieldDescriptor.TYPE_INT32: int,
    FieldDescriptor.TYPE_INT64: long,
    FieldDescriptor.TYPE_UINT32: int,
    FieldDescriptor.TYPE_UINT64: long,
    FieldDescriptor.TYPE_SINT32: int,
    FieldDescriptor.TYPE_SINT64: long,
    FieldDescriptor.TYPE_FIXED32: int,
    FieldDescriptor.TYPE_FIXED64: long,
    FieldDescriptor.TYPE_SFIXED32: int,
    FieldDescriptor.TYPE_SFIXED64: long,
    FieldDescriptor.TYPE_BOOL: bool,
    FieldDescriptor.TYPE_STRING: unicode,
    FieldDescriptor.TYPE_BYTES: lambda b: b.encode("base64"),
    FieldDescriptor.TYPE_ENUM: int,
}

REVERSE_TYPE_CALLABLE_MAP = {
    FieldDescriptor.TYPE_BYTES: lambda b: b.decode("base64"),
}

只要把 TYPE_CALLABLE_MAP 傳進去 protobuf_to_dict 或 REVERSE_TYPE_CALLABLE_MAP 傳進 dict_to_protobuf即可。

python
protobuf_to_dict(pb, type_callable_map=TYPE_CALLABLE_MAP)
dict_to_protobuf(pb, my_dict, type_callable_map=REVERSE_TYPE_CALLABLE_MAP)

參考 ​

Changelog

Just observe 👀