| // 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 <google/protobuf/io/zero_copy_stream_impl_lite.h> |
| #include <gflags/gflags.h> |
| #include "json2pb/zero_copy_stream_writer.h" |
| #include "json2pb/encode_decode.h" |
| #include "json2pb/protobuf_map.h" |
| #include "json2pb/rapidjson.h" |
| #include "json2pb/pb_to_json.h" |
| #include "json2pb/protobuf_type_resolver.h" |
| |
| #include "butil/base64.h" |
| #include "butil/iobuf.h" |
| #include "butil/strings/string_util.h" |
| |
| namespace json2pb { |
| |
| DECLARE_int32(json2pb_max_recursion_depth); |
| |
| // Helper function to check if the maximum depth of a message is exceeded. |
| bool ExceedMaxDepth(const google::protobuf::Message& message, int current_depth) { |
| if (current_depth > FLAGS_json2pb_max_recursion_depth) { |
| return true; |
| } |
| |
| const google::protobuf::Descriptor* descriptor = message.GetDescriptor(); |
| const google::protobuf::Reflection* reflection = message.GetReflection(); |
| |
| std::vector<const google::protobuf::FieldDescriptor*> fields; |
| // Collect declared fields. |
| for (int i = 0; i < descriptor->field_count(); ++i) { |
| fields.push_back(descriptor->field(i)); |
| } |
| // Collect extension fields (if any). |
| { |
| std::vector<const google::protobuf::FieldDescriptor*> ext_fields; |
| descriptor->file()->pool()->FindAllExtensions(descriptor, &ext_fields); |
| fields.insert(fields.end(), ext_fields.begin(), ext_fields.end()); |
| } |
| |
| for (const auto* field : fields) { |
| if (field->cpp_type() != google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { |
| continue; |
| } |
| |
| if (field->is_repeated()) { |
| const int count = reflection->FieldSize(message, field); |
| for (int j = 0; j < count; ++j) { |
| const google::protobuf::Message& sub_message = |
| reflection->GetRepeatedMessage(message, field, j); |
| if (ExceedMaxDepth(sub_message, current_depth + 1)) { |
| return true; |
| } |
| } |
| } else { |
| if (reflection->HasField(message, field)) { |
| const google::protobuf::Message& sub_message = |
| reflection->GetMessage(message, field); |
| if (ExceedMaxDepth(sub_message, current_depth + 1)) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| 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, int depth = 0); |
| |
| 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, int depth); |
| |
| std::string _error; |
| Pb2JsonOptions _option; |
| }; |
| |
| template <typename Handler> |
| bool PbToJsonConverter::Convert(const google::protobuf::Message& message, Handler& handler, int depth) { |
| if (depth > FLAGS_json2pb_max_recursion_depth) { |
| _error = "Exceeded maximum recursion depth"; |
| return false; |
| } |
| 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 < 4024000 |
| 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 (depth == 0 && _option.single_repeated_to_array) { |
| if (map_fields.empty() && fields.size() == 1 && fields.front()->is_repeated()) { |
| return _PbFieldToJson(message, fields.front(), handler, depth); |
| } |
| } |
| |
| 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: " + butil::EnsureString(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 = butil::EnsureString(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, depth)) { |
| 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 = butil::EnsureString(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, depth)) { |
| 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, int depth) { |
| 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 = butil::EnsureString(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 = |
| butil::EnsureString(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, depth + 1)) { |
| return false; |
| } |
| } |
| handler.EndArray(field_size); |
| |
| } else { |
| if (!Convert(reflection->GetMessage(message, field), handler, depth + 1)) { |
| 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); |
| } else { |
| BUTIL_RAPIDJSON_NAMESPACE::OptimizedWriter<OutputStream> writer(os); |
| succ = converter.Convert(message, writer); |
| } |
| 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); |
| } |
| |
| bool ProtoMessageToProtoJson(const google::protobuf::Message& message, |
| google::protobuf::io::ZeroCopyOutputStream* json, |
| const Pb2ProtoJsonOptions& options, std::string* error) { |
| if (ExceedMaxDepth(message, 0)) { |
| if (error) { |
| *error = "Exceeded maximum recursion depth"; |
| } |
| return false; |
| } |
| butil::IOBuf buf; |
| butil::IOBufAsZeroCopyOutputStream output_stream(&buf); |
| if (!message.SerializeToZeroCopyStream(&output_stream)) { |
| return false; |
| } |
| |
| TypeResolverUniqueptr type_resolver = GetTypeResolver(message); |
| butil::IOBufAsZeroCopyInputStream input_stream(buf); |
| auto st = google::protobuf::util::BinaryToJsonStream( |
| type_resolver.get(), GetTypeUrl(message), &input_stream, json, options); |
| |
| bool ok = st.ok(); |
| if (!ok && NULL != error) { |
| *error = st.ToString(); |
| } |
| return ok; |
| } |
| |
| bool ProtoMessageToProtoJson(const google::protobuf::Message& message, std::string* json, |
| const Pb2ProtoJsonOptions& options, std::string* error) { |
| google::protobuf::io::StringOutputStream output_stream(json); |
| return ProtoMessageToProtoJson(message, &output_stream, options, error); |
| } |
| |
| } // namespace json2pb |