diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/protobuf/json/proto2json_printer.cpp | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/protobuf/json/proto2json_printer.cpp')
-rw-r--r-- | library/cpp/protobuf/json/proto2json_printer.cpp | 517 |
1 files changed, 517 insertions, 0 deletions
diff --git a/library/cpp/protobuf/json/proto2json_printer.cpp b/library/cpp/protobuf/json/proto2json_printer.cpp new file mode 100644 index 0000000000..6123eab0f2 --- /dev/null +++ b/library/cpp/protobuf/json/proto2json_printer.cpp @@ -0,0 +1,517 @@ +#include "proto2json_printer.h" +#include "config.h" +#include "util.h" + +#include <util/generic/yexception.h> +#include <util/string/ascii.h> +#include <util/string/cast.h> + +namespace NProtobufJson { + using namespace NProtoBuf; + + class TJsonKeyBuilder { + public: + TJsonKeyBuilder(const FieldDescriptor& field, const TProto2JsonConfig& config, TString& tmpBuf) + : NewKeyStr(tmpBuf) + { + if (config.NameGenerator) { + NewKeyStr = config.NameGenerator(field); + NewKeyBuf = NewKeyStr; + return; + } + + if (config.UseJsonName) { + Y_ASSERT(!field.json_name().empty()); + NewKeyStr = field.json_name(); + if (!field.has_json_name() && !NewKeyStr.empty()) { + // FIXME: https://st.yandex-team.ru/CONTRIB-139 + NewKeyStr[0] = AsciiToLower(NewKeyStr[0]); + } + NewKeyBuf = NewKeyStr; + return; + } + + switch (config.FieldNameMode) { + case TProto2JsonConfig::FieldNameOriginalCase: { + NewKeyBuf = field.name(); + break; + } + + case TProto2JsonConfig::FieldNameLowerCase: { + NewKeyStr = field.name(); + NewKeyStr.to_lower(); + NewKeyBuf = NewKeyStr; + break; + } + + case TProto2JsonConfig::FieldNameUpperCase: { + NewKeyStr = field.name(); + NewKeyStr.to_upper(); + NewKeyBuf = NewKeyStr; + break; + } + + case TProto2JsonConfig::FieldNameCamelCase: { + NewKeyStr = field.name(); + if (!NewKeyStr.empty()) { + NewKeyStr[0] = AsciiToLower(NewKeyStr[0]); + } + NewKeyBuf = NewKeyStr; + break; + } + + case TProto2JsonConfig::FieldNameSnakeCase: { + NewKeyStr = field.name(); + ToSnakeCase(&NewKeyStr); + NewKeyBuf = NewKeyStr; + break; + } + + case TProto2JsonConfig::FieldNameSnakeCaseDense: { + NewKeyStr = field.name(); + ToSnakeCaseDense(&NewKeyStr); + NewKeyBuf = NewKeyStr; + break; + } + + default: + Y_VERIFY_DEBUG(false, "Unknown FieldNameMode."); + } + } + + const TStringBuf& GetKey() const { + return NewKeyBuf; + } + + private: + TStringBuf NewKeyBuf; + TString& NewKeyStr; + }; + + TProto2JsonPrinter::TProto2JsonPrinter(const TProto2JsonConfig& cfg) + : Config(cfg) + { + } + + TProto2JsonPrinter::~TProto2JsonPrinter() { + } + + TStringBuf TProto2JsonPrinter::MakeKey(const FieldDescriptor& field) { + return TJsonKeyBuilder(field, GetConfig(), TmpBuf).GetKey(); + } + + template <bool InMapContext, typename T> + std::enable_if_t<InMapContext, void> WriteWithMaybeEmptyKey(IJsonOutput& json, const TStringBuf& key, const T& value) { + json.WriteKey(key).Write(value); + } + + template <bool InMapContext, typename T> + std::enable_if_t<!InMapContext, void> WriteWithMaybeEmptyKey(IJsonOutput& array, const TStringBuf& key, const T& value) { + Y_ASSERT(!key); + array.Write(value); + } + + template <bool InMapContext> + void TProto2JsonPrinter::PrintStringValue(const FieldDescriptor& field, + const TStringBuf& key, const TString& value, + IJsonOutput& json) { + if (!GetConfig().StringTransforms.empty()) { + TString tmpBuf = value; + for (const TStringTransformPtr& stringTransform : GetConfig().StringTransforms) { + Y_ASSERT(stringTransform); + if (stringTransform) { + if (field.type() == FieldDescriptor::TYPE_BYTES) + stringTransform->TransformBytes(tmpBuf); + else + stringTransform->Transform(tmpBuf); + } + } + WriteWithMaybeEmptyKey<InMapContext>(json, key, tmpBuf); + } else { + WriteWithMaybeEmptyKey<InMapContext>(json, key, value); + } + } + + template <bool InMapContext> + void TProto2JsonPrinter::PrintEnumValue(const TStringBuf& key, + const EnumValueDescriptor* value, + IJsonOutput& json) { + if (Config.EnumValueGenerator) { + WriteWithMaybeEmptyKey<InMapContext>(json, key, Config.EnumValueGenerator(*value)); + return; + } + + switch (GetConfig().EnumMode) { + case TProto2JsonConfig::EnumNumber: { + WriteWithMaybeEmptyKey<InMapContext>(json, key, value->number()); + break; + } + + case TProto2JsonConfig::EnumName: { + WriteWithMaybeEmptyKey<InMapContext>(json, key, value->name()); + break; + } + + case TProto2JsonConfig::EnumFullName: { + WriteWithMaybeEmptyKey<InMapContext>(json, key, value->full_name()); + break; + } + + case TProto2JsonConfig::EnumNameLowerCase: { + TString newName = value->name(); + newName.to_lower(); + WriteWithMaybeEmptyKey<InMapContext>(json, key, newName); + break; + } + + case TProto2JsonConfig::EnumFullNameLowerCase: { + TString newName = value->full_name(); + newName.to_lower(); + WriteWithMaybeEmptyKey<InMapContext>(json, key, newName); + break; + } + + default: + Y_VERIFY_DEBUG(false, "Unknown EnumMode."); + } + } + + void TProto2JsonPrinter::PrintSingleField(const Message& proto, + const FieldDescriptor& field, + IJsonOutput& json, + TStringBuf key) { + Y_VERIFY(!field.is_repeated(), "field is repeated."); + + if (!key) { + key = MakeKey(field); + } + +#define FIELD_TO_JSON(EProtoCppType, ProtoGet) \ + case FieldDescriptor::EProtoCppType: { \ + json.WriteKey(key).Write(reflection->ProtoGet(proto, &field)); \ + break; \ + } + +#define INT_FIELD_TO_JSON(EProtoCppType, ProtoGet) \ + case FieldDescriptor::EProtoCppType: { \ + const auto value = reflection->ProtoGet(proto, &field); \ + if (NeedStringifyNumber(value)) { \ + json.WriteKey(key).Write(ToString(value)); \ + } else { \ + json.WriteKey(key).Write(value); \ + } \ + break; \ + } + + const Reflection* reflection = proto.GetReflection(); + + bool shouldPrintField = reflection->HasField(proto, &field); + if (!shouldPrintField && GetConfig().MissingSingleKeyMode == TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired) { + if (field.has_default_value()) { + shouldPrintField = true; + } else if (field.is_required()) { + ythrow yexception() << "Empty required protobuf field: " + << field.full_name() << "."; + } + } + shouldPrintField = shouldPrintField || GetConfig().MissingSingleKeyMode == TProto2JsonConfig::MissingKeyDefault; + + if (shouldPrintField) { + switch (field.cpp_type()) { + INT_FIELD_TO_JSON(CPPTYPE_INT32, GetInt32); + INT_FIELD_TO_JSON(CPPTYPE_INT64, GetInt64); + INT_FIELD_TO_JSON(CPPTYPE_UINT32, GetUInt32); + INT_FIELD_TO_JSON(CPPTYPE_UINT64, GetUInt64); + FIELD_TO_JSON(CPPTYPE_DOUBLE, GetDouble); + FIELD_TO_JSON(CPPTYPE_FLOAT, GetFloat); + FIELD_TO_JSON(CPPTYPE_BOOL, GetBool); + + case FieldDescriptor::CPPTYPE_MESSAGE: { + json.WriteKey(key); + Print(reflection->GetMessage(proto, &field), json); + break; + } + + case FieldDescriptor::CPPTYPE_ENUM: { + PrintEnumValue<true>(key, reflection->GetEnum(proto, &field), json); + break; + } + + case FieldDescriptor::CPPTYPE_STRING: { + TString scratch; + const TString& value = reflection->GetStringReference(proto, &field, &scratch); + PrintStringValue<true>(field, key, value, json); + break; + } + + default: + ythrow yexception() << "Unknown protobuf field type: " + << static_cast<int>(field.cpp_type()) << "."; + } + } else { + switch (GetConfig().MissingSingleKeyMode) { + case TProto2JsonConfig::MissingKeyNull: { + json.WriteKey(key).WriteNull(); + break; + } + + case TProto2JsonConfig::MissingKeySkip: + case TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired: + default: + break; + } + } +#undef FIELD_TO_JSON + } + + void TProto2JsonPrinter::PrintRepeatedField(const Message& proto, + const FieldDescriptor& field, + IJsonOutput& json, + TStringBuf key) { + Y_VERIFY(field.is_repeated(), "field isn't repeated."); + + const bool isMap = field.is_map() && GetConfig().MapAsObject; + if (!key) { + key = MakeKey(field); + } + +#define REPEATED_FIELD_TO_JSON(EProtoCppType, ProtoGet) \ + case FieldDescriptor::EProtoCppType: { \ + for (size_t i = 0, endI = reflection->FieldSize(proto, &field); i < endI; ++i) \ + json.Write(reflection->ProtoGet(proto, &field, i)); \ + break; \ + } + + const Reflection* reflection = proto.GetReflection(); + + if (reflection->FieldSize(proto, &field) > 0) { + json.WriteKey(key); + if (isMap) { + json.BeginObject(); + } else { + json.BeginList(); + } + + switch (field.cpp_type()) { + REPEATED_FIELD_TO_JSON(CPPTYPE_INT32, GetRepeatedInt32); + REPEATED_FIELD_TO_JSON(CPPTYPE_INT64, GetRepeatedInt64); + REPEATED_FIELD_TO_JSON(CPPTYPE_UINT32, GetRepeatedUInt32); + REPEATED_FIELD_TO_JSON(CPPTYPE_UINT64, GetRepeatedUInt64); + REPEATED_FIELD_TO_JSON(CPPTYPE_DOUBLE, GetRepeatedDouble); + REPEATED_FIELD_TO_JSON(CPPTYPE_FLOAT, GetRepeatedFloat); + REPEATED_FIELD_TO_JSON(CPPTYPE_BOOL, GetRepeatedBool); + + case FieldDescriptor::CPPTYPE_MESSAGE: { + if (isMap) { + for (size_t i = 0, endI = reflection->FieldSize(proto, &field); i < endI; ++i) { + PrintKeyValue(reflection->GetRepeatedMessage(proto, &field, i), json); + } + } else { + for (size_t i = 0, endI = reflection->FieldSize(proto, &field); i < endI; ++i) { + Print(reflection->GetRepeatedMessage(proto, &field, i), json); + } + } + break; + } + + case FieldDescriptor::CPPTYPE_ENUM: { + for (int i = 0, endI = reflection->FieldSize(proto, &field); i < endI; ++i) + PrintEnumValue<false>(TStringBuf(), reflection->GetRepeatedEnum(proto, &field, i), json); + break; + } + + case FieldDescriptor::CPPTYPE_STRING: { + TString scratch; + for (int i = 0, endI = reflection->FieldSize(proto, &field); i < endI; ++i) { + const TString& value = + reflection->GetRepeatedStringReference(proto, &field, i, &scratch); + PrintStringValue<false>(field, TStringBuf(), value, json); + } + break; + } + + default: + ythrow yexception() << "Unknown protobuf field type: " + << static_cast<int>(field.cpp_type()) << "."; + } + + if (isMap) { + json.EndObject(); + } else { + json.EndList(); + } + } else { + switch (GetConfig().MissingRepeatedKeyMode) { + case TProto2JsonConfig::MissingKeyNull: { + json.WriteKey(key).WriteNull(); + break; + } + + case TProto2JsonConfig::MissingKeyDefault: { + json.WriteKey(key); + if (isMap) { + json.BeginObject().EndObject(); + } else { + json.BeginList().EndList(); + } + break; + } + + case TProto2JsonConfig::MissingKeySkip: + case TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired: + default: + break; + } + } + +#undef REPEATED_FIELD_TO_JSON + } + + void TProto2JsonPrinter::PrintKeyValue(const NProtoBuf::Message& proto, + IJsonOutput& json) { + const FieldDescriptor* keyField = proto.GetDescriptor()->FindFieldByName("key"); + Y_VERIFY(keyField, "Map entry key field not found."); + TString key = MakeKey(proto, *keyField); + const FieldDescriptor* valueField = proto.GetDescriptor()->FindFieldByName("value"); + Y_VERIFY(valueField, "Map entry value field not found."); + PrintField(proto, *valueField, json, key); + } + + TString TProto2JsonPrinter::MakeKey(const NProtoBuf::Message& proto, + const NProtoBuf::FieldDescriptor& field) { + const Reflection* reflection = proto.GetReflection(); + TString result; + switch (field.cpp_type()) { + case FieldDescriptor::CPPTYPE_INT32: + result = ToString(reflection->GetInt32(proto, &field)); + break; + case FieldDescriptor::CPPTYPE_INT64: + result = ToString(reflection->GetInt64(proto, &field)); + break; + case FieldDescriptor::CPPTYPE_UINT32: + result = ToString(reflection->GetUInt32(proto, &field)); + break; + case FieldDescriptor::CPPTYPE_UINT64: + result = ToString(reflection->GetUInt64(proto, &field)); + break; + case FieldDescriptor::CPPTYPE_DOUBLE: + result = ToString(reflection->GetDouble(proto, &field)); + break; + case FieldDescriptor::CPPTYPE_FLOAT: + result = ToString(reflection->GetFloat(proto, &field)); + break; + case FieldDescriptor::CPPTYPE_BOOL: + result = ToString(reflection->GetBool(proto, &field)); + break; + case FieldDescriptor::CPPTYPE_ENUM: { + const EnumValueDescriptor* value = reflection->GetEnum(proto, &field); + switch (GetConfig().EnumMode) { + case TProto2JsonConfig::EnumNumber: + result = ToString(value->number()); + break; + case TProto2JsonConfig::EnumName: + result = value->name(); + break; + case TProto2JsonConfig::EnumFullName: + result = value->full_name(); + break; + case TProto2JsonConfig::EnumNameLowerCase: + result = value->name(); + result.to_lower(); + break; + case TProto2JsonConfig::EnumFullNameLowerCase: + result = value->full_name(); + result.to_lower(); + break; + default: + ythrow yexception() << "Unsupported enum mode."; + } + break; + } + case FieldDescriptor::CPPTYPE_STRING: + result = reflection->GetString(proto, &field); + break; + default: + ythrow yexception() << "Unsupported key type."; + } + + return result; + } + + void TProto2JsonPrinter::PrintField(const Message& proto, + const FieldDescriptor& field, + IJsonOutput& json, + const TStringBuf key) { + + + if (field.is_repeated()) + PrintRepeatedField(proto, field, json, key); + else + PrintSingleField(proto, field, json, key); + } + + void TProto2JsonPrinter::Print(const Message& proto, IJsonOutput& json, bool closeMap) { + const Descriptor* descriptor = proto.GetDescriptor(); + Y_ASSERT(descriptor); + + json.BeginObject(); + + // Iterate over all non-extension fields + for (int f = 0, endF = descriptor->field_count(); f < endF; ++f) { + const FieldDescriptor* field = descriptor->field(f); + Y_ASSERT(field); + PrintField(proto, *field, json); + } + + // Check extensions via ListFields + std::vector<const FieldDescriptor*> fields; + auto* ref = proto.GetReflection(); + ref->ListFields(proto, &fields); + + for (const FieldDescriptor* field : fields) { + Y_ASSERT(field); + if (field->is_extension()) { + switch (GetConfig().ExtensionFieldNameMode) { + case TProto2JsonConfig::ExtFldNameFull: + PrintField(proto, *field, json, field->full_name()); + break; + case TProto2JsonConfig::ExtFldNameShort: + PrintField(proto, *field, json); + break; + } + } + } + + if (closeMap) { + json.EndObject(); + } + } + + template <class T, class U> + std::enable_if_t<!std::is_unsigned<T>::value, bool> ValueInRange(T value, U range) { + return value >= -range && value <= range; + } + + template <class T, class U> + std::enable_if_t<std::is_unsigned<T>::value, bool> ValueInRange(T value, U range) { + return value <= (std::make_unsigned_t<U>)(range); + } + + template <class T> + bool TProto2JsonPrinter::NeedStringifyNumber(T value) const { + constexpr long SAFE_INTEGER_RANGE_FLOAT = 16777216; + constexpr long long SAFE_INTEGER_RANGE_DOUBLE = 9007199254740992; + + switch (GetConfig().StringifyLongNumbers) { + case TProto2JsonConfig::StringifyLongNumbersNever: + return false; + case TProto2JsonConfig::StringifyLongNumbersForFloat: + return !ValueInRange(value, SAFE_INTEGER_RANGE_FLOAT); + case TProto2JsonConfig::StringifyLongNumbersForDouble: + return !ValueInRange(value, SAFE_INTEGER_RANGE_DOUBLE); + } + + return false; + } + +} |