# 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](https://developers.google.com/protocol-buffers/docs/proto3#maps))。這在 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)
```

# 參考

*   [https://github.com/benhodgson/protobuf-to-dict](https://github.com/benhodgson/protobuf-to-dict)
*   [https://github.com/wearefair/protobuf-to-dict](https://github.com/wearefair/protobuf-to-dict)
