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

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

·

2 min read

簡介

protobuf_to_dictprotobuf3_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

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。

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 進去。

# 舊版本用到的轉換函數

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_dictREVERSE_TYPE_CALLABLE_MAP 傳進 dict_to_protobuf即可。

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

參考