aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp
diff options
context:
space:
mode:
authorsashashkov <sashashkov@yandex-team.com>2024-06-11 13:03:18 +0300
committersashashkov <sashashkov@yandex-team.com>2024-06-12 10:26:35 +0300
commit67e7cccc330744cdb98035e309f626c1f63afb84 (patch)
treef29f638710ae5223dbb7a56e427aea6c551bdff6 /library/cpp
parenta26a1f012a93e209458200c2ba8ae484a45a6c54 (diff)
downloadydb-67e7cccc330744cdb98035e309f626c1f63afb84.tar.gz
Implement number stringification for repeated fields as well
4ab5908e416439366466d984fc08db7254401884
Diffstat (limited to 'library/cpp')
-rw-r--r--library/cpp/protobuf/json/config.h11
-rw-r--r--library/cpp/protobuf/json/proto2json_printer.cpp40
-rw-r--r--library/cpp/protobuf/json/proto2json_printer.h3
-rw-r--r--library/cpp/protobuf/json/ut/proto2json_ut.cpp193
4 files changed, 242 insertions, 5 deletions
diff --git a/library/cpp/protobuf/json/config.h b/library/cpp/protobuf/json/config.h
index d17ac7b15b..98d484cdf4 100644
--- a/library/cpp/protobuf/json/config.h
+++ b/library/cpp/protobuf/json/config.h
@@ -86,15 +86,18 @@ namespace NProtobufJson {
/// Print map as object, otherwise print it as array of key/value objects
bool MapAsObject = false;
- /// Stringify long integers which are not exactly representable by float or double values
enum EStringifyNumbersMode {
StringifyLongNumbersNever = 0, // default
StringifyLongNumbersForFloat,
StringifyLongNumbersForDouble,
StringifyInt64Always,
};
+ /// Stringify long integers which are not exactly representable by float or double values. Not affect repeated numbers, for repeated use StringifyNumbersRepeated
EStringifyNumbersMode StringifyNumbers = StringifyLongNumbersNever;
+ /// Stringify repeated long integers which are not exactly representable by float or double values. May cause heterogenous arrays, use StringifyInt64Always or StringifyLongNumbersNever to avoid
+ EStringifyNumbersMode StringifyNumbersRepeated = StringifyLongNumbersNever;
+
/// Decode Any fields content
bool ConvertAny = false;
@@ -191,6 +194,11 @@ namespace NProtobufJson {
return *this;
}
+ TSelf& SetStringifyNumbersRepeated(EStringifyNumbersMode stringify) {
+ StringifyNumbersRepeated = stringify;
+ return *this;
+ }
+
TSelf& SetNameGenerator(TNameGenerator callback) {
NameGenerator = callback;
return *this;
@@ -210,6 +218,7 @@ namespace NProtobufJson {
ConvertAny = value;
return *this;
}
+
};
}
diff --git a/library/cpp/protobuf/json/proto2json_printer.cpp b/library/cpp/protobuf/json/proto2json_printer.cpp
index bf0f9eb60d..5d0e140615 100644
--- a/library/cpp/protobuf/json/proto2json_printer.cpp
+++ b/library/cpp/protobuf/json/proto2json_printer.cpp
@@ -360,6 +360,19 @@ namespace NProtobufJson {
break; \
}
+#define REPEATED_INT_FIELD_TO_JSON(EProtoCppType, ProtoGet) \
+ case FieldDescriptor::EProtoCppType: { \
+ for (size_t i = 0, endI = reflection->FieldSize(proto, &field); i < endI; ++i) { \
+ const auto value = reflection->ProtoGet(proto, &field, i); \
+ if (NeedStringifyRepeatedNumber(value)) { \
+ json.Write(ToString(value)); \
+ } else { \
+ json.Write(value); \
+ } \
+ } \
+ break; \
+ }
+
const Reflection* reflection = proto.GetReflection();
if (reflection->FieldSize(proto, &field) > 0) {
@@ -371,10 +384,10 @@ namespace NProtobufJson {
}
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_INT_FIELD_TO_JSON(CPPTYPE_INT32, GetRepeatedInt32);
+ REPEATED_INT_FIELD_TO_JSON(CPPTYPE_INT64, GetRepeatedInt64);
+ REPEATED_INT_FIELD_TO_JSON(CPPTYPE_UINT32, GetRepeatedUInt32);
+ REPEATED_INT_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);
@@ -596,4 +609,23 @@ namespace NProtobufJson {
return false;
}
+ template <class T>
+ bool TProto2JsonPrinter::NeedStringifyRepeatedNumber(T value) const {
+ constexpr long SAFE_INTEGER_RANGE_FLOAT = 1L << 24;
+ constexpr long long SAFE_INTEGER_RANGE_DOUBLE = 1LL << 53;
+
+ switch (GetConfig().StringifyNumbersRepeated) {
+ 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);
+ case TProto2JsonConfig::StringifyInt64Always:
+ return std::is_same_v<T, i64> || std::is_same_v<T, ui64>;
+ }
+
+ return false;
+ }
+
}
diff --git a/library/cpp/protobuf/json/proto2json_printer.h b/library/cpp/protobuf/json/proto2json_printer.h
index 360e66ad2f..c3547dab6b 100644
--- a/library/cpp/protobuf/json/proto2json_printer.h
+++ b/library/cpp/protobuf/json/proto2json_printer.h
@@ -60,6 +60,9 @@ namespace NProtobufJson {
template <class T>
bool NeedStringifyNumber(T value) const;
+ template <class T>
+ bool NeedStringifyRepeatedNumber(T value) const;
+
bool TryPrintAny(const NProtoBuf::Message& proto, IJsonOutput& json);
void PrintFields(const NProtoBuf::Message& proto, IJsonOutput& json);
diff --git a/library/cpp/protobuf/json/ut/proto2json_ut.cpp b/library/cpp/protobuf/json/ut/proto2json_ut.cpp
index 9b74e73752..9e98ab8a06 100644
--- a/library/cpp/protobuf/json/ut/proto2json_ut.cpp
+++ b/library/cpp/protobuf/json/ut/proto2json_ut.cpp
@@ -1086,6 +1086,199 @@ Y_UNIT_TEST(TestStringifyNumbers) {
#undef TEST_SINGLE
} // TestStringifyNumbers
+Y_UNIT_TEST(TestStringifyNumbersRepeated) {
+#define TEST_SINGLE(flag, field, value) \
+ do { \
+ TFlatRepeated proto; \
+ proto.Add##field(value); \
+ \
+ TStringStream jsonStr; \
+ TProto2JsonConfig config; \
+ config.SetStringifyNumbers(flag); \
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); \
+ UNIT_ASSERT_EQUAL(jsonStr.Str(), "{\"" #field "\":[" #value "]}"); \
+ } while (false)
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, 1);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, 10000000000000000);
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, 1);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, 10000000000000000);
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, 1);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, 10000000000000000);
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, UI64, 1);
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, UI64, 10000000000000000);
+
+#undef TEST_SINGLE
+} // TestStringifyNumbersRepeated
+
+Y_UNIT_TEST(TestStringifyNumbersRepeatedStringification){
+#define TEST_SINGLE(flag, field, value, expectString) \
+ do { \
+ TFlatRepeated proto; \
+ proto.Add##field(value); \
+ \
+ TStringStream jsonStr; \
+ TProto2JsonConfig config; \
+ config.SetStringifyNumbersRepeated(flag); \
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); \
+ if (expectString) { \
+ UNIT_ASSERT_EQUAL(jsonStr.Str(), "{\"" #field "\":[\"" #value "\"]}"); \
+ } else { \
+ UNIT_ASSERT_EQUAL(jsonStr.Str(), "{\"" #field "\":[" #value "]}"); \
+ } \
+ } while (false)
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, 1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, 1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, 10000000000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, -1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, -1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, -10000000000000000, false);
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, 1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, 1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, 10000000000000000, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, -1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, -1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, -10000000000000000, true);
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, 1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, 1000000000, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, 10000000000000000, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, -1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, -1000000000, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, -10000000000000000, true);
+
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, UI64, 1, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, UI32, 1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, UI64, 10000000000000000, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, SI64, -1, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, SI32, -1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, SI64, -10000000000000000, true);
+
+#undef TEST_SINGLE
+} // TestStringifyNumbersRepeatedStringification
+
+Y_UNIT_TEST(TestStringifyNumbersRepeatedStringificationList){
+ using NJson::JSON_STRING;
+ using NJson::JSON_UINTEGER;
+ using NJson::JSON_INTEGER;
+
+ TFlatRepeated proto;
+ proto.AddUI64(1);
+ proto.AddUI64(1000000000);
+ proto.AddUI64(10000000000000000);
+ proto.AddSI64(1);
+ proto.AddSI64(1000000000);
+ proto.AddSI64(10000000000000000);
+ proto.AddSI64(-1);
+ proto.AddSI64(-1000000000);
+ proto.AddSI64(-10000000000000000);
+ proto.AddUI32(1);
+ proto.AddUI32(1000000000);
+ proto.AddSI32(-1);
+ proto.AddSI32(-1000000000);
+
+ TProto2JsonConfig config;
+ NJson::TJsonValue jsonValue;
+ THashMap<TString, NJson::TJsonValue> jsonMap;
+ {
+ jsonValue = NJson::TJsonValue{};
+ config.SetStringifyNumbersRepeated(TProto2JsonConfig::StringifyLongNumbersNever);
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonValue, config));
+ jsonMap = jsonValue.GetMap();
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[0].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[1].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[2].GetType(), JSON_UINTEGER);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[0].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[1].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[2].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[3].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[4].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[5].GetType(), JSON_INTEGER);
+ }
+ {
+ jsonValue = NJson::TJsonValue{};
+ config.SetStringifyNumbersRepeated(TProto2JsonConfig::StringifyLongNumbersForDouble);
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonValue, config));
+ jsonMap = jsonValue.GetMap();
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[0].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[1].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[2].GetType(), JSON_STRING);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[0].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[1].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[2].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[3].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[4].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[5].GetType(), JSON_STRING);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI32")[0].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI32")[1].GetType(), JSON_INTEGER);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI32")[0].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI32")[1].GetType(), JSON_UINTEGER);
+ }
+ {
+ jsonValue = NJson::TJsonValue{};
+ config.SetStringifyNumbersRepeated(TProto2JsonConfig::StringifyLongNumbersForFloat);
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonValue, config));
+ jsonMap = jsonValue.GetMap();
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[0].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[1].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[2].GetType(), JSON_STRING);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[0].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[1].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[2].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[3].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[4].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[5].GetType(), JSON_STRING);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI32")[0].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI32")[1].GetType(), JSON_STRING);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI32")[0].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI32")[1].GetType(), JSON_STRING);
+ }
+ {
+ jsonValue = NJson::TJsonValue{};
+ config.SetStringifyNumbersRepeated(TProto2JsonConfig::StringifyInt64Always);
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonValue, config));
+ jsonMap = jsonValue.GetMap();
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[0].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[1].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[2].GetType(), JSON_STRING);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[0].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[1].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[2].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[3].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[4].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[5].GetType(), JSON_STRING);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI32")[0].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI32")[1].GetType(), JSON_INTEGER);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI32")[0].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI32")[1].GetType(), JSON_UINTEGER);
+ }
+
+} // TestStringifyNumbersRepeatedStringificationList
+
Y_UNIT_TEST(TestExtension) {
TExtensionField proto;
proto.SetExtension(bar, 1);