pb2json.hpp 文档:Protobuf 与 JSON 通用转换工具类
一、文件概述
pb2json.hpp
是一个基于 C++ 的通用工具类头文件,用于实现 Protobuf 消息与 JSON 格式(通过 jsoncpp 库)的双向转换。该类利用 Protobuf 的反射(Reflection)机制,支持任意 Protobuf 消息类型的转换,无需为特定消息编写专用代码。
二、核心功能
- 支持 Protobuf 消息与 jsoncpp 的
Json::Value
互转 - 支持 Protobuf 消息与 JSON 字符串互转
- 兼容 Protobuf 常见数据类型(基础类型、枚举、嵌套消息、重复字段等)
- 提供枚举类型转换选项(转为字符串或整数)
三、代码实现(内嵌完整代码)
cpp
#ifndef MYLIB_JSON_2_PB
# define MYLIB_JSON_2_PB
#include "jsoncpp/json/json.h"
#include "stdint.h"
#include <google/protobuf/descriptor.h>
#include <google/protobuf/message.h>
class NpJson2Pb {
public:
typedef ::google::protobuf::Message ProtobufMsg;
typedef ::google::protobuf::Reflection ProtobufReflection;
typedef ::google::protobuf::FieldDescriptor ProtobufFieldDescriptor;
typedef ::google::protobuf::Descriptor ProtobufDescriptor;
public:
/**
* Protobuf 消息转 JSON 字符串
* @param src 源 Protobuf 消息
* @param dst 输出的 JSON 字符串
* @param enum2str 枚举是否转为字符串(默认 true)
*/
static void PbMsg2JsonStr(const ProtobufMsg& src, std::string& dst, bool enum2str = true)
{
Json::Value value;
PbMsg2Json(src, value, enum2str);
Json::FastWriter writer;
dst = writer.write(value);
}
/**
* JSON 字符串转 Protobuf 消息
* @param src 源 JSON 字符串
* @param dst 输出的 Protobuf 消息
* @param str2enum 是否从字符串解析枚举(默认 true)
* @return 转换成功返回 true,失败返回 false
*/
static bool JsonStr2PbMsg(const std::string& src, ProtobufMsg& dst, bool str2enum = true)
{
Json::Value value;
Json::Reader reader(Json::Features::strictMode());
if (!reader.parse(src, value))
{
printf("[JsonStr2PbMsg]parse json string is fail,str=%s", src.c_str());
return false;
}
if(true != Json2PbMsg(value, dst, str2enum))
{
printf("[JsonStr2PbMsg]pb convert error");
return false;
}
return true;
}
/**
* Json::Value 转 Protobuf 消息
* @param src 源 Json::Value 对象
* @param dst 输出的 Protobuf 消息
* @param str2enum 是否从字符串解析枚举(默认 true)
* @return 转换成功返回 true,失败返回 false
*/
static bool Json2PbMsg(const Json::Value& src, ProtobufMsg& dst, bool str2enum = true)
{
const ProtobufDescriptor* descriptor = dst.GetDescriptor();
const ProtobufReflection* reflection = dst.GetReflection();
if (NULL == descriptor || NULL == reflection) return false;
int32_t count = descriptor->field_count();
for (int32_t i = 0; i < count; ++i)
{
const ProtobufFieldDescriptor* field = descriptor->field(i);
if (NULL == field) continue;
if(!src.isMember(field->name()))
{
continue;
}
const Json::Value& value = src[field->name()];
if (field->is_repeated())
{
if (!value.isArray())
{
printf("[Json2PbMsg] Json is not ary!");
return false;
}
else
{
Json2RepeatedMessage(value, dst, field, reflection, str2enum);
continue;
}
}
switch (field->type())
{
case ProtobufFieldDescriptor::TYPE_BOOL:
{
if (value.isBool())
{
reflection->SetBool(&dst, field, value.asBool());
}
else if (value.isInt())
{
reflection->SetBool(&dst, field, value.isInt());
}
else if (value.isString())
{
if (value.asString() == "true")
reflection->SetBool(&dst, field, true);
else if (value.asString() == "false")
reflection->SetBool(&dst, field, false);
}
break;
}
case ProtobufFieldDescriptor::TYPE_ENUM:
{
const ::google::protobuf::EnumDescriptor *pedesc = field->enum_type();
const ::google::protobuf::EnumValueDescriptor* pevdesc = NULL;
if (str2enum)
{
pevdesc = pedesc->FindValueByName(value.asString());
}
else
{
pevdesc = pedesc->FindValueByNumber(value.asInt());
}
if (NULL != pevdesc)
{
reflection->SetEnum(&dst, field, pevdesc);
}
break;
}
case ProtobufFieldDescriptor::TYPE_INT32:
case ProtobufFieldDescriptor::TYPE_SINT32:
case ProtobufFieldDescriptor::TYPE_SFIXED32:
{
if (value.isInt())
{
reflection->SetInt32(&dst, field, value.asInt());
}
break;
}
case ProtobufFieldDescriptor::TYPE_UINT32:
case ProtobufFieldDescriptor::TYPE_FIXED32:
{
if (value.isUInt() || value.isInt())
{
reflection->SetUInt32(&dst, field, value.asUInt());
}
break;
}
case ProtobufFieldDescriptor::TYPE_INT64:
case ProtobufFieldDescriptor::TYPE_SINT64:
case ProtobufFieldDescriptor::TYPE_SFIXED64:
{
if (value.isInt())
{
reflection->SetInt64(&dst, field, npatoll(value.asString().c_str()));
}
break;
}
case ProtobufFieldDescriptor::TYPE_UINT64:
case ProtobufFieldDescriptor::TYPE_FIXED64:
{
if (value.isUInt() || value.isInt())
{
reflection->SetUInt64(&dst, field, npatoll(value.asString().c_str()));
}
break;
}
case ProtobufFieldDescriptor::TYPE_FLOAT:
{
if (value.isDouble())
{
reflection->SetFloat(&dst, field, atof(value.asString().c_str()));
}
break;
}
case ProtobufFieldDescriptor::TYPE_DOUBLE:
{
if (value.isDouble())
{
reflection->SetDouble(&dst, field, value.asDouble());
}
break;
}
case ProtobufFieldDescriptor::TYPE_STRING:
case ProtobufFieldDescriptor::TYPE_BYTES:
{
if (value.isString())
{
reflection->SetString(&dst, field, value.asString());
}
break;
}
case ProtobufFieldDescriptor::TYPE_MESSAGE:
{
if (value.isObject())
{
Json2PbMsg(value, *reflection->MutableMessage(&dst, field), str2enum);
}
break;
}
default:
{
printf("[Json2PbMsg] no invalid Type!");
break;
}
}
}
return true;
}
/**
* Protobuf 消息转 Json::Value
* @param src 源 Protobuf 消息
* @param dst 输出的 Json::Value 对象
* @param enum2str 枚举是否转为字符串(默认 true)
*/
static void PbMsg2Json(const ProtobufMsg& src, Json::Value& dst, bool enum2str = true)
{
const ProtobufDescriptor* descriptor = src.GetDescriptor();
const ProtobufReflection* reflection = src.GetReflection();
if (NULL == descriptor || NULL == descriptor) return;
int32_t count = descriptor->field_count();
for (int32_t i = 0; i < count; ++i)
{
const ProtobufFieldDescriptor* field = descriptor->field(i);
if (field->is_repeated())
{
if (reflection->FieldSize(src, field) > 0)
RepeatedMessage2Json(src, field, reflection, dst[field->name()], enum2str);
continue;
}
if (!reflection->HasField(src, field))
{
continue;
}
switch (field->type())
{
case ProtobufFieldDescriptor::TYPE_MESSAGE:
{
ProtobufMsg* ptMsg = (ProtobufMsg*)&src;
const ProtobufMsg* tmp_message = reflection->MutableMessage(ptMsg, field);
if (0 != tmp_message->ByteSize())
{
PbMsg2Json(*tmp_message, dst[field->name()], enum2str);
}
break;
}
case ProtobufFieldDescriptor::TYPE_BOOL:
dst[field->name()] = reflection->GetBool(src, field) ? true : false;
break;
case ProtobufFieldDescriptor::TYPE_ENUM:
{
const ::google::protobuf::EnumValueDescriptor* enum_value_desc = reflection->GetEnum(src, field);
if (enum2str) {
dst[field->name()] = enum_value_desc->name();
} else {
dst[field->name()] = enum_value_desc->number();
}
break;
}
case ProtobufFieldDescriptor::TYPE_INT32:
case ProtobufFieldDescriptor::TYPE_SINT32:
case ProtobufFieldDescriptor::TYPE_SFIXED32:
dst[field->name()] = Json::Int(reflection->GetInt32(src, field));
break;
case ProtobufFieldDescriptor::TYPE_UINT32:
case ProtobufFieldDescriptor::TYPE_FIXED32:
dst[field->name()] = Json::UInt(reflection->GetUInt32(src, field));
break;
case ProtobufFieldDescriptor::TYPE_INT64:
case ProtobufFieldDescriptor::TYPE_SINT64:
case ProtobufFieldDescriptor::TYPE_SFIXED64:
{
static char int64str[32];
memset(int64str, 0, sizeof(int64str));
sprintf(int64str, "%lld", (s64)reflection->GetRepeatedInt64(src, field, i));
dst[field->name()] = int64str;
}
// jsoncpp 不支持 INT64,暂时使用 string
break;
case ProtobufFieldDescriptor::TYPE_UINT64:
case ProtobufFieldDescriptor::TYPE_FIXED64:
{
static char uint64str[32];
memset(uint64str, 0, sizeof(uint64str));
sprintf(uint64str, "%llu", (u64)reflection->GetRepeatedInt64(src, field, i));
dst[field->name()] = uint64str;
}
// jsoncpp 不支持 UINT64,暂时使用 string
break;
case ProtobufFieldDescriptor::TYPE_FLOAT:
dst[field->name()] = reflection->GetFloat(src, field);
break;
case ProtobufFieldDescriptor::TYPE_DOUBLE:
{
dst[field->name()] = reflection->GetDouble(src, field);
break;
}
case ProtobufFieldDescriptor::TYPE_STRING:
case ProtobufFieldDescriptor::TYPE_BYTES:
dst[field->name()] = reflection->GetString(src, field);
break;
default:
printf("[PbMsg2Json] no invalid Type!");
break;
}
}
}
private:
/**
* JSON 数组转 Protobuf 重复字段
* @param json 源 JSON 数组
* @param message 目标 Protobuf 消息
* @param field 字段描述符
* @param reflection 反射接口
* @param str2enum 是否从字符串解析枚举
* @return 转换成功返回 true
*/
static bool Json2RepeatedMessage(const Json::Value& json, ProtobufMsg& message,
const ProtobufFieldDescriptor* field,
const ProtobufReflection* reflection,
bool str2enum = true)
{
int32_t count = json.size();
Json::Value jsonforinex;
for (int32_t j = 0; j < count; ++j)
{
if (json[j].type() == Json::objectValue && field->type() != ProtobufFieldDescriptor::TYPE_MESSAGE)
{
jsonforinex = json[j][field->name()];
}
else
{
jsonforinex = json[j];
}
switch (field->type())
{
case ProtobufFieldDescriptor::TYPE_BOOL:
{
if (jsonforinex.isBool())
{
reflection->AddBool(&message, field, jsonforinex.asBool());
}
else if (jsonforinex.isInt())
{
reflection->AddBool(&message, field, jsonforinex.asInt());
}
else if (jsonforinex.isString())
{
if (jsonforinex.asString() == "true")
{
reflection->AddBool(&message, field, true);
}
else if (jsonforinex.asString() == "false")
{
reflection->AddBool(&message, field, false);
}
}
break;
}
case ProtobufFieldDescriptor::TYPE_ENUM:
{
const ::google::protobuf::EnumDescriptor *pedesc = field->enum_type();
const ::google::protobuf::EnumValueDescriptor* pevdesc = NULL;
if (str2enum)
{
pevdesc = pedesc->FindValueByName(jsonforinex.asString());
}
else
{
pevdesc = pedesc->FindValueByNumber(jsonforinex.asInt());
}
if (NULL != pevdesc)
{
reflection->AddEnum(&message, field, pevdesc);
}
break;
}
case ProtobufFieldDescriptor::TYPE_INT32:
case ProtobufFieldDescriptor::TYPE_SINT32:
case ProtobufFieldDescriptor::TYPE_SFIXED32:
{
if (jsonforinex.isInt())
{
reflection->AddInt32(&message, field, jsonforinex.asInt());
}
} break;
case ProtobufFieldDescriptor::TYPE_UINT32:
case ProtobufFieldDescriptor::TYPE_FIXED32:
{
if (jsonforinex.isUInt() || jsonforinex.isInt())
{
reflection->AddUInt32(&message, field, jsonforinex.asUInt());
}
} break;
case ProtobufFieldDescriptor::TYPE_INT64:
case ProtobufFieldDescriptor::TYPE_SINT64:
case ProtobufFieldDescriptor::TYPE_SFIXED64:
{
if (jsonforinex.isInt())
{
reflection->AddInt64(&message, field, npatoll(jsonforinex.asString().c_str()));
}
} break;
case ProtobufFieldDescriptor::TYPE_UINT64:
case ProtobufFieldDescriptor::TYPE_FIXED64:
{
if (jsonforinex.isUInt() || jsonforinex.isInt())
{
reflection->AddUInt64(&message, field, npatoll(jsonforinex.asString().c_str()));
}
} break;
case ProtobufFieldDescriptor::TYPE_FLOAT:
{
if (jsonforinex.isDouble())
{
reflection->AddFloat(&message, field, atof(jsonforinex.asString().c_str()));
}
} break;
case ProtobufFieldDescriptor::TYPE_DOUBLE:
{
if (jsonforinex.isDouble())
{
reflection->AddDouble(&message, field, jsonforinex.asDouble());
}
} break;
case ProtobufFieldDescriptor::TYPE_MESSAGE:
{
if (jsonforinex.isObject())
{
Json2PbMsg(jsonforinex, *reflection->AddMessage(&message, field), str2enum);
}
} break;
case ProtobufFieldDescriptor::TYPE_STRING:
case ProtobufFieldDescriptor::TYPE_BYTES:
{
if (jsonforinex.isString())
{
reflection->AddString(&message, field, jsonforinex.asString());
}
} break;
default:
{
printf("[Json2RepeatedMessage] no invalid Type!");
break;
}
}
}
return true;
}
/**
* Protobuf 重复字段转 JSON 数组
* @param message 源 Protobuf 消息
* @param field 字段描述符
* @param reflection 反射接口
* @param json 输出的 JSON 数组
* @param enum2str 枚举是否转为字符串
*/
static void RepeatedMessage2Json(const ProtobufMsg& message,
const ProtobufFieldDescriptor* field,
const ProtobufReflection* reflection,
Json::Value& json, bool enum2str)
{
if (NULL == field || NULL == reflection)
{
PbMsg2Json(message, json, enum2str);
}
for (int32_t i = 0; i < reflection->FieldSize(message, field); ++i)
{
Json::Value tmp_json;
switch (field->type())
{
case ProtobufFieldDescriptor::TYPE_BOOL:
tmp_json[field->name()] = reflection->GetRepeatedBool(message, field, i) ? true : false;
break;
case ProtobufFieldDescriptor::TYPE_ENUM:
{
const ::google::protobuf::EnumValueDescriptor* enum_value_desc = reflection->GetRepeatedEnum(message, field, i);
if (enum2str)
{
tmp_json = enum_value_desc->name();
}
else
{
tmp_json = enum_value_desc->number();
}
break;
}
case ProtobufFieldDescriptor::TYPE_INT32:
case ProtobufFieldDescriptor::TYPE_SINT32:
case ProtobufFieldDescriptor::TYPE_SFIXED32:
tmp_json[field->name()] = reflection->GetRepeatedInt32(message, field, i);
break;
case ProtobufFieldDescriptor::TYPE_UINT32:
case ProtobufFieldDescriptor::TYPE_FIXED32:
tmp_json[field->name()] = reflection->GetRepeatedUInt32(message, field, i);
break;
case ProtobufFieldDescriptor::TYPE_INT64:
case ProtobufFieldDescriptor::TYPE_SINT64:
case ProtobufFieldDescriptor::TYPE_SFIXED64:
{
static char int64str[32];
memset(int64str, 0, sizeof(int64str));
sprintf(int64str, "%lld", (s64)reflection->GetRepeatedInt64(message, field, i));
tmp_json[field->name()] = int64str;
}
break;
case ProtobufFieldDescriptor::TYPE_UINT64:
case ProtobufFieldDescriptor::TYPE_FIXED64:
{
static char uint64str[32];
memset(uint64str, 0, sizeof(uint64str));
sprintf(uint64str, "%llu", (u64)reflection->GetRepeatedInt64(message, field, i));
tmp_json[field->name()] = uint64str;
}
break;
case ProtobufFieldDescriptor::TYPE_FLOAT:
tmp_json[field->name()] = reflection->GetRepeatedFloat(message, field, i);
break;
case ProtobufFieldDescriptor::TYPE_DOUBLE:
{
tmp_json[field->name()] = reflection->GetRepeatedDouble(message, field, i);
break;
}
case ProtobufFieldDescriptor::TYPE_STRING:
case ProtobufFieldDescriptor::TYPE_BYTES:
tmp_json[field->name()] = reflection->GetRepeatedString(message, field, i);
break;
case ProtobufFieldDescriptor::TYPE_MESSAGE:
{
const ProtobufMsg& tmp_message = reflection->GetRepeatedMessage(message, field, i);
if (0 != tmp_message.ByteSize())
{
PbMsg2Json(tmp_message, tmp_json, enum2str);
}
break;
}
default:
{
printf("[RepeatedMessage2Json] no invalid Type!");
}
break;
}
json.append(tmp_json);
}
}
/**
* 字符串转长整型(处理 int64/uint64 转换)
* @param p 输入字符串
* @return 转换后的长整型值
*/
static long long npatoll(const char *p)
{
long long n;
int c, neg = 0;
unsigned char *up = (unsigned char *)p;
if (!isdigit(c = *up)) {
while (isspace(c))
c = *++up;
switch (c) {
case '-':
neg++;
/* FALLTHROUGH */
case '+':
c = *++up;
}
if (!isdigit(c))
return (0);
}
for (n = '0' - c; isdigit(c = *++up); ) {
n *= 10; /* 分两步避免溢出 */
n += '0' - c; /* 累加负数避免最大值处理问题 */
}
return (neg ? n : -n);
}
};
#endif
四、类与方法说明
1. 核心类:NpJson2Pb
- 静态类,所有方法均为静态,无需实例化即可使用
- 依赖:
jsoncpp
库(处理 JSON)和Protobuf
库(提供反射接口) - 类型别名:
ProtobufMsg
:google::protobuf::Message
(Protobuf 消息基类)ProtobufReflection
:google::protobuf::Reflection
(反射接口)ProtobufFieldDescriptor
:google::protobuf::FieldDescriptor
(字段描述符)ProtobufDescriptor
:google::protobuf::Descriptor
(消息描述符)
2. 主要转换方法
方法名 | 功能 | 参数说明 |
---|---|---|
PbMsg2JsonStr |
Protobuf 消息 → JSON 字符串 | src :源 Protobuf 消息 dst :输出 JSON 字符串 enum2str :枚举是否转为字符串(默认 true) |
JsonStr2PbMsg |
JSON 字符串 → Protobuf 消息 | src :源 JSON 字符串 dst :输出 Protobuf 消息 str2enum :是否从字符串解析枚举(默认 true) 返回值:成功返回 true |
PbMsg2Json |
Protobuf 消息 → Json::Value |
src :源 Protobuf 消息 dst :输出 Json::Value 对象 enum2str :枚举是否转为字符串(默认 true) |
Json2PbMsg |
Json::Value → Protobuf 消息 |
src :源 Json::Value 对象 dst :输出 Protobuf 消息 str2enum :是否从字符串解析枚举(默认 true) 返回值:成功返回 true |
3. 辅助方法(私有)
Json2RepeatedMessage
:处理 JSON 数组到 Protobuf 重复字段(repeated)的转换RepeatedMessage2Json
:处理 Protobuf 重复字段到 JSON 数组的转换npatoll
:自定义字符串转长整型函数,解决 64 位整数转换精度问题
五、支持的数据类型
- 基础类型:bool、int32、uint32、int64、uint64、float、double
- 字符串类型:string、bytes
- 复合类型:枚举(enum)、嵌套消息(message)、重复字段(repeated,数组)
六、实现细节
-
反射机制 :通过 Protobuf 的
Descriptor
(描述符)和Reflection
(反射)接口,动态获取消息结构和字段值,实现通用转换(无需为特定消息编写代码)。 -
64位整数处理:由于 jsoncpp 对 int64/uint64 支持有限,转换时以字符串形式存储,避免精度丢失。
-
枚举类型转换:
- 可选转为字符串(枚举名)或整数(枚举值)
- 转换选项通过
enum2str
(Pb→Json)和str2enum
(Json→Pb)参数控制
-
嵌套消息 :通过递归调用
PbMsg2Json
和Json2PbMsg
处理嵌套结构。 -
错误处理 :转换失败时通过
printf
输出错误信息(可根据需求改为异常抛出)。
七、使用示例
cpp
// 假设已定义 User 类型的 Protobuf 消息
#include "user.pb.h"
#include "pb2json.hpp"
// Protobuf 转 JSON 字符串
User user;
user.set_id(1001);
user.set_name("Alice");
std::string json_str;
NpJson2Pb::PbMsg2JsonStr(user, json_str); // 转换为 JSON 字符串
// JSON 字符串转 Protobuf
User user2;
bool ret = NpJson2Pb::JsonStr2PbMsg(json_str, user2); // 转换回 Protobuf
if (ret) {
// 转换成功,使用 user2
}
八、注意事项
- 依赖库需正确链接:编译时需链接
protobuf
和jsoncpp
库。 - 64位整数以字符串存储:JSON 中 int64/uint64 类型会转为字符串,解析时需注意。
- 字段名匹配:JSON 字段名需与 Protobuf 字段名完全一致(区分大小写)。
- 枚举兼容性:若 JSON 中枚举字符串与 Protobuf 枚举名不匹配,转换会失败。
- 错误处理:当前通过
printf
输出错误,生产环境可改为日志或异常机制。
该工具类适用于需要在 Protobuf 与 JSON 之间进行灵活转换的场景(如 API 交互、配置解析等),支持任意 Protobuf 消息类型,通用性强。