| // 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 |