blob: b0066dc6dc5be74dabac8550e431a5b1f6b13195 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <sys/time.h>
#include <time.h>
#include <google/protobuf/descriptor.h>
#include "butil/base64.h"
#include "zero_copy_stream_writer.h"
#include "encode_decode.h"
#include "protobuf_map.h"
#include "rapidjson.h"
#include "pb_to_json.h"
namespace json2pb {
Pb2JsonOptions::Pb2JsonOptions()
: enum_option(OUTPUT_ENUM_BY_NAME)
, pretty_json(false)
, enable_protobuf_map(true)
#ifdef BAIDU_INTERNAL
, bytes_to_base64(false)
#else
, bytes_to_base64(true)
#endif
, jsonify_empty_array(false)
, always_print_primitive_fields(false)
, single_repeated_to_array(false) {
}
class PbToJsonConverter {
public:
explicit PbToJsonConverter(const Pb2JsonOptions& opt) : _option(opt) {}
template <typename Handler>
bool Convert(const google::protobuf::Message& message, Handler& handler, bool root_msg = false);
const std::string& ErrorText() const { return _error; }
private:
template <typename Handler>
bool _PbFieldToJson(const google::protobuf::Message& message,
const google::protobuf::FieldDescriptor* field,
Handler& handler);
std::string _error;
Pb2JsonOptions _option;
};
template <typename Handler>
bool PbToJsonConverter::Convert(const google::protobuf::Message& message, Handler& handler, bool root_msg) {
const google::protobuf::Reflection* reflection = message.GetReflection();
const google::protobuf::Descriptor* descriptor = message.GetDescriptor();
int ext_range_count = descriptor->extension_range_count();
int field_count = descriptor->field_count();
std::vector<const google::protobuf::FieldDescriptor*> fields;
fields.reserve(64);
for (int i = 0; i < ext_range_count; ++i) {
const google::protobuf::Descriptor::ExtensionRange*
ext_range = descriptor->extension_range(i);
#if GOOGLE_PROTOBUF_VERSION < 4025000
for (int tag_number = ext_range->start; tag_number < ext_range->end; ++tag_number)
#else
for (int tag_number = ext_range->start_number(); tag_number < ext_range->end_number(); ++tag_number)
#endif
{
const google::protobuf::FieldDescriptor* field =
reflection->FindKnownExtensionByNumber(tag_number);
if (field) {
fields.push_back(field);
}
}
}
std::vector<const google::protobuf::FieldDescriptor*> map_fields;
for (int i = 0; i < field_count; ++i) {
const google::protobuf::FieldDescriptor* field = descriptor->field(i);
if (_option.enable_protobuf_map && json2pb::IsProtobufMap(field)) {
map_fields.push_back(field);
} else {
fields.push_back(field);
}
}
if (root_msg && _option.single_repeated_to_array) {
if (map_fields.empty() && fields.size() == 1 && fields.front()->is_repeated()) {
return _PbFieldToJson(message, fields.front(), handler);
}
}
handler.StartObject();
// Fill in non-map fields
std::string field_name_str;
for (size_t i = 0; i < fields.size(); ++i) {
const google::protobuf::FieldDescriptor* field = fields[i];
if (!field->is_repeated() && !reflection->HasField(message, field)) {
// Field that has not been set
if (field->is_required()) {
_error = "Missing required field: " + field->full_name();
return false;
}
// Whether dumps default fields
if (!_option.always_print_primitive_fields) {
continue;
}
} else if (field->is_repeated()
&& reflection->FieldSize(message, field) == 0
&& !_option.jsonify_empty_array) {
// Repeated field that has no entry
continue;
}
const std::string& orig_name = field->name();
bool decoded = decode_name(orig_name, field_name_str);
const std::string& name = decoded ? field_name_str : orig_name;
handler.Key(name.data(), name.size(), false);
if (!_PbFieldToJson(message, field, handler)) {
return false;
}
}
// Fill in map fields
for (size_t i = 0; i < map_fields.size(); ++i) {
const google::protobuf::FieldDescriptor* map_desc = map_fields[i];
const google::protobuf::FieldDescriptor* key_desc =
map_desc->message_type()->field(json2pb::KEY_INDEX);
const google::protobuf::FieldDescriptor* value_desc =
map_desc->message_type()->field(json2pb::VALUE_INDEX);
// Write a json object corresponding to hold protobuf map
// such as {"key": value, ...}
const std::string& orig_name = map_desc->name();
bool decoded = decode_name(orig_name, field_name_str);
const std::string& name = decoded ? field_name_str : orig_name;
handler.Key(name.data(), name.size(), false);
handler.StartObject();
std::string entry_name;
for (int j = 0; j < reflection->FieldSize(message, map_desc); ++j) {
const google::protobuf::Message& entry =
reflection->GetRepeatedMessage(message, map_desc, j);
const google::protobuf::Reflection* entry_reflection = entry.GetReflection();
entry_name = entry_reflection->GetStringReference(
entry, key_desc, &entry_name);
handler.Key(entry_name.data(), entry_name.size(), false);
// Fill in entries into this json object
if (!_PbFieldToJson(entry, value_desc, handler)) {
return false;
}
}
// Hack: Pass 0 as parameter since Writer doesn't care this
handler.EndObject(0);
}
// Hack: Pass 0 as parameter since Writer doesn't care this
handler.EndObject(0);
return true;
}
template <typename Handler>
bool PbToJsonConverter::_PbFieldToJson(
const google::protobuf::Message& message,
const google::protobuf::FieldDescriptor* field,
Handler& handler) {
const google::protobuf::Reflection* reflection = message.GetReflection();
switch (field->cpp_type()) {
#define CASE_FIELD_TYPE(cpptype, method, valuetype, handle) \
case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype: { \
if (field->is_repeated()) { \
int field_size = reflection->FieldSize(message, field); \
handler.StartArray(); \
for (int index = 0; index < field_size; ++index) { \
handler.handle(static_cast<valuetype>( \
reflection->GetRepeated##method( \
message, field, index))); \
} \
handler.EndArray(field_size); \
\
} else { \
handler.handle(static_cast<valuetype>( \
reflection->Get##method(message, field))); \
} \
break; \
}
CASE_FIELD_TYPE(BOOL, Bool, bool, Bool);
CASE_FIELD_TYPE(INT32, Int32, int, AddInt);
CASE_FIELD_TYPE(UINT32, UInt32, unsigned int, AddUint);
CASE_FIELD_TYPE(INT64, Int64, int64_t, AddInt64);
CASE_FIELD_TYPE(UINT64, UInt64, uint64_t, AddUint64);
CASE_FIELD_TYPE(FLOAT, Float, double, Double);
CASE_FIELD_TYPE(DOUBLE, Double, double, Double);
#undef CASE_FIELD_TYPE
case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
std::string value;
if (field->is_repeated()) {
int field_size = reflection->FieldSize(message, field);
handler.StartArray();
for (int index = 0; index < field_size; ++index) {
value = reflection->GetRepeatedStringReference(
message, field, index, &value);
if (field->type() == google::protobuf::FieldDescriptor::TYPE_BYTES
&& _option.bytes_to_base64) {
std::string value_decoded;
butil::Base64Encode(value, &value_decoded);
handler.String(value_decoded.data(), value_decoded.size(), false);
} else {
handler.String(value.data(), value.size(), false);
}
}
handler.EndArray(field_size);
} else {
value = reflection->GetStringReference(message, field, &value);
if (field->type() == google::protobuf::FieldDescriptor::TYPE_BYTES
&& _option.bytes_to_base64) {
std::string value_decoded;
butil::Base64Encode(value, &value_decoded);
handler.String(value_decoded.data(), value_decoded.size(), false);
} else {
handler.String(value.data(), value.size(), false);
}
}
break;
}
case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
if (field->is_repeated()) {
int field_size = reflection->FieldSize(message, field);
handler.StartArray();
if (_option.enum_option == OUTPUT_ENUM_BY_NAME) {
for (int index = 0; index < field_size; ++index) {
const std::string& enum_name = reflection->GetRepeatedEnum(
message, field, index)->name();
handler.String(enum_name.data(), enum_name.size(), false);
}
} else {
for (int index = 0; index < field_size; ++index) {
handler.AddInt(reflection->GetRepeatedEnum(
message, field, index)->number());
}
}
handler.EndArray();
} else {
if (_option.enum_option == OUTPUT_ENUM_BY_NAME) {
const std::string& enum_name =
reflection->GetEnum(message, field)->name();
handler.String(enum_name.data(), enum_name.size(), false);
} else {
handler.AddInt(reflection->GetEnum(message, field)->number());
}
}
break;
}
case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
if (field->is_repeated()) {
int field_size = reflection->FieldSize(message, field);
handler.StartArray();
for (int index = 0; index < field_size; ++index) {
if (!Convert(reflection->GetRepeatedMessage(
message, field, index), handler)) {
return false;
}
}
handler.EndArray(field_size);
} else {
if (!Convert(reflection->GetMessage(message, field), handler)) {
return false;
}
}
break;
}
}
return true;
}
template <typename OutputStream>
bool ProtoMessageToJsonStream(const google::protobuf::Message& message,
const Pb2JsonOptions& options,
OutputStream& os, std::string* error) {
PbToJsonConverter converter(options);
bool succ = false;
if (options.pretty_json) {
BUTIL_RAPIDJSON_NAMESPACE::PrettyWriter<OutputStream> writer(os);
succ = converter.Convert(message, writer, true);
} else {
BUTIL_RAPIDJSON_NAMESPACE::OptimizedWriter<OutputStream> writer(os);
succ = converter.Convert(message, writer, true);
}
if (!succ && error) {
error->clear();
error->append(converter.ErrorText());
}
return succ;
}
bool ProtoMessageToJson(const google::protobuf::Message& message,
std::string* json,
const Pb2JsonOptions& options,
std::string* error) {
// TODO(gejun): We could further wrap a std::string as a buffer to reduce
// a copying.
BUTIL_RAPIDJSON_NAMESPACE::StringBuffer buffer;
if (json2pb::ProtoMessageToJsonStream(message, options, buffer, error)) {
json->append(buffer.GetString(), buffer.GetSize());
return true;
}
return false;
}
bool ProtoMessageToJson(const google::protobuf::Message& message,
std::string* json, std::string* error) {
return ProtoMessageToJson(message, json, Pb2JsonOptions(), error);
}
bool ProtoMessageToJson(const google::protobuf::Message& message,
google::protobuf::io::ZeroCopyOutputStream *stream,
const Pb2JsonOptions& options, std::string* error) {
json2pb::ZeroCopyStreamWriter wrapper(stream);
return json2pb::ProtoMessageToJsonStream(message, options, wrapper, error);
}
bool ProtoMessageToJson(const google::protobuf::Message& message,
google::protobuf::io::ZeroCopyOutputStream *stream,
std::string* error) {
return ProtoMessageToJson(message, stream, Pb2JsonOptions(), error);
}
} // namespace json2pb