diff options
author | sashashkov <sashashkov@yandex-team.com> | 2024-06-11 13:03:18 +0300 |
---|---|---|
committer | sashashkov <sashashkov@yandex-team.com> | 2024-06-12 10:26:35 +0300 |
commit | 67e7cccc330744cdb98035e309f626c1f63afb84 (patch) | |
tree | f29f638710ae5223dbb7a56e427aea6c551bdff6 /library/cpp/protobuf/json | |
parent | a26a1f012a93e209458200c2ba8ae484a45a6c54 (diff) | |
download | ydb-67e7cccc330744cdb98035e309f626c1f63afb84.tar.gz |
Implement number stringification for repeated fields as well
4ab5908e416439366466d984fc08db7254401884
Diffstat (limited to 'library/cpp/protobuf/json')
-rw-r--r-- | library/cpp/protobuf/json/config.h | 11 | ||||
-rw-r--r-- | library/cpp/protobuf/json/proto2json_printer.cpp | 40 | ||||
-rw-r--r-- | library/cpp/protobuf/json/proto2json_printer.h | 3 | ||||
-rw-r--r-- | library/cpp/protobuf/json/ut/proto2json_ut.cpp | 193 |
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); |