json2pb
json2pb
学习如何将 json 转换为 pb。
brpc 支持 json 与 protobuf 之间的双向转换,实现位于 json2pb,json 解析使用 rapidjson。该功能对 pb 2.x 和 3.x 均有效。pb3 内置了 json 转换 的功能。
按照设计,通过 HTTP + json 访问 protobuf 服务是对外提供服务的常见方式,因此转换必须精准,转换规则列举如下。
message
对应 rapidjson 的 Object,用花括号包围,其中的元素会被递归地解析。
// protobuf
message Foo {
required string field1 = 1;
required int32 field2 = 2;
}
message Bar {
required Foo foo = 1;
optional bool flag = 2;
required string name = 3;
}
// rapidjson
{"foo":{"field1":"hello", "field2":3},"name":"Tom" }重复字段
对应 rapidjson 的 Array,以方括号包围,其中的元素会被递归地解析。与 message 不同,每个元素的类型相同。
// protobuf
repeated int32 numbers = 1;
// rapidjson
{"numbers" : [12, 17, 1, 24] }特别地,对于仅包含一个 repeated 类型成员的 message,序列化为 json 时支持直接序列化为数组,以简化包体。
// protobuf
message Foo {
required int32 numbers = 1;
}
// rapidjson
[12, 17, 1, 24]该特性默认为关闭状态,客户端在发送请求时,或服务端在发送回复时,可手动开启:
brpc::Controller cntl;
cntl.set_pb_single_repeated_to_array(true);map
满足如下条件的 repeated MSG 被视作 json map:
- MSG 包含一个名为 key 的字段,类型为 string,tag 为 1。
- MSG 包含一个名为 value 的字段,tag 为 2。
- 不包含其他字段。
这种"map"的属性有:
- 自然不能确保 key 有序或不重复,用户视需求自行检查。
- 与 protobuf 3.x 中的 map 二进制兼容,故 3.x 中的 map 使用 pb2json 也会正确地转化为 json map。
如果符合所有条件的 repeated MSG 并不需要被认为是 json map,打破上面任一条件就行了:在 MSG 中加入 optional int32 this_message_is_not_map_entry = 3; 这个办法破坏了"不包含其他字段"这项,且不影响二进制兼容。也可以调换 key 和 value 的 tag 值,让前者为 2 后者为 1,也使条件不再满足。
integers
rapidjson 会根据值打上对应的类型标记,比如:
- 对于 3,rapidjson 中的 IsUInt、IsInt、IsUint64、IsInt64 等函数均会返回 true。
- 对于 -1,则 IsUInt 和 IsUint64 会返回 false。
- 对于 5000000000,IsUInt 和 IsInt 是 false。
这使得我们不用特殊处理,转化代码就可以自动地把 json 中的 UInt 填入 protobuf 中的 int64,而不是机械地认为这两个类型不匹配。相应地,转化代码自然能识别 overflow 和 underflow,当出现时会转化失败。
// protobuf
int32 uint32 int64 uint64
// rapidjson
Int UInt Int64 UInt64floating point
json 的整数类型也可以转至 pb 的浮点数类型。浮点数(IEEE754)除了普通数字外还接受 "NaN"、"Infinity"、"-Infinity" 三个字符串,分别对应 Not A Number、正无穷、负无穷。
// protobuf
float double
// rapidjson
Float Double Int Uint Int64 Uint64enum
enum 可转换为整数,也可转换为其名字对应的字符串,具体由 Pb2JsonOptions.enum_options 控制,默认转换为后者。
string
默认同名转换。但当 JSON 中出现非法的 C++ 变量名(即 pb 的变量名规则)时,仍允许转换,规则为:
illegal-char <-> **_Z**<ASCII-of-the-char>**_**
bytes
与 string 不同,可能包含 \\0 的 bytes 默认以 base64 编码。
// protobuf
"Hello, World!"
// json
"SGVsbG8sIFdvcmxkIQo="bool
对应 json 的 true / false。
unknown fields
unknown_fields → json 目前不支持,未来可能支持。json → unknown_fields 目前也未支持,即 protobuf 无法透传 json 中不认识的字段。原因在于 protobuf 真正的 key 是 proto 文件中每个字段后的数字:
...
required int32 foo = 3; <-- the real key
...这也是 unknown_fields 的 key。当一个 protobuf 不认识某个字段时,其 proto 中必然不会有那个数字,所以没办法插入 unknown_fields。
可行的方案有几种:
- 确保被 json 访问的服务的 proto 文件最新。这样就不需要透传了,但越前端的服务越类似 proxy,可能并不现实。
- protobuf 中定义特殊透传字段。比如名为 unknown_json_fields,在解析对应的 protobuf 时特殊处理。此方案修改面广且对性能有一定影响,有明确需求时再议。
最后修改于 2022 年 6 月 13 日:更新 getting_start 与 json2pb 文档 (devlive-community/knowforge#73) (b6b734ea7)
评论
登录后参与评论
KnowForge