aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/protobuf/json/ut
diff options
context:
space:
mode:
authorDevtools Arcadia <arcadia-devtools@yandex-team.ru>2022-02-07 18:08:42 +0300
committerDevtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net>2022-02-07 18:08:42 +0300
commit1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch)
treee26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/protobuf/json/ut
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/protobuf/json/ut')
-rw-r--r--library/cpp/protobuf/json/ut/fields.incl23
-rw-r--r--library/cpp/protobuf/json/ut/filter_ut.cpp93
-rw-r--r--library/cpp/protobuf/json/ut/filter_ut.proto20
-rw-r--r--library/cpp/protobuf/json/ut/inline_ut.cpp122
-rw-r--r--library/cpp/protobuf/json/ut/inline_ut.proto29
-rw-r--r--library/cpp/protobuf/json/ut/json.h69
-rw-r--r--library/cpp/protobuf/json/ut/json2proto_ut.cpp1147
-rw-r--r--library/cpp/protobuf/json/ut/proto.h62
-rw-r--r--library/cpp/protobuf/json/ut/proto2json_ut.cpp1022
-rw-r--r--library/cpp/protobuf/json/ut/repeated_fields.incl21
-rw-r--r--library/cpp/protobuf/json/ut/string_transform_ut.cpp106
-rw-r--r--library/cpp/protobuf/json/ut/test.proto203
-rw-r--r--library/cpp/protobuf/json/ut/util_ut.cpp42
-rw-r--r--library/cpp/protobuf/json/ut/ya.make23
14 files changed, 2982 insertions, 0 deletions
diff --git a/library/cpp/protobuf/json/ut/fields.incl b/library/cpp/protobuf/json/ut/fields.incl
new file mode 100644
index 0000000000..4b22985836
--- /dev/null
+++ b/library/cpp/protobuf/json/ut/fields.incl
@@ -0,0 +1,23 @@
+// Intentionally no #pragma once
+
+// (Field name == JSON key, Value)
+DEFINE_FIELD(I32, Min<i32>())
+DEFINE_FIELD(I64, Min<i64>())
+DEFINE_FIELD(UI32, Max<ui32>())
+DEFINE_FIELD(UI64, Max<ui64>())
+DEFINE_FIELD(SI32, Min<i32>())
+DEFINE_FIELD(SI64, Min<i64>())
+DEFINE_FIELD(FI32, Max<ui32>())
+DEFINE_FIELD(FI64, Max<ui64>())
+DEFINE_FIELD(SFI32, Min<i32>())
+DEFINE_FIELD(SFI64, Min<i64>())
+DEFINE_FIELD(Bool, true)
+DEFINE_FIELD(String, "Lorem ipsum")
+DEFINE_FIELD(Bytes, "מחשב")
+DEFINE_FIELD(Enum, E_1)
+DEFINE_FIELD(Float, 1.123f)
+DEFINE_FIELD(Double, 1.123456789012)
+DEFINE_FIELD(OneString, "Lorem ipsum dolor")
+DEFINE_FIELD(OneTwoString, "Lorem ipsum dolor sit")
+DEFINE_FIELD(ABC, "abc")
+DEFINE_FIELD(UserID, "some_id") \ No newline at end of file
diff --git a/library/cpp/protobuf/json/ut/filter_ut.cpp b/library/cpp/protobuf/json/ut/filter_ut.cpp
new file mode 100644
index 0000000000..95c227666f
--- /dev/null
+++ b/library/cpp/protobuf/json/ut/filter_ut.cpp
@@ -0,0 +1,93 @@
+#include <library/cpp/protobuf/json/ut/filter_ut.pb.h>
+
+#include <library/cpp/protobuf/json/filter.h>
+#include <library/cpp/protobuf/json/field_option.h>
+#include <library/cpp/protobuf/json/proto2json.h>
+#include <library/cpp/testing/unittest/registar.h>
+
+using namespace NProtobufJson;
+
+static NProtobufJsonUt::TFilterTest GetTestMsg() {
+ NProtobufJsonUt::TFilterTest msg;
+ msg.SetOptFiltered("1");
+ msg.SetNotFiltered("23");
+ msg.AddRepFiltered(45);
+ msg.AddRepFiltered(67);
+ msg.MutableInner()->AddNumber(100);
+ msg.MutableInner()->AddNumber(200);
+ msg.MutableInner()->SetInnerFiltered(235);
+ return msg;
+}
+
+Y_UNIT_TEST_SUITE(TProto2JsonFilterTest){
+ Y_UNIT_TEST(TestFilterPrinter){
+ NProtobufJsonUt::TFilterTest msg = GetTestMsg();
+{
+ TString expected = R"({"OptFiltered":"1","NotFiltered":"23","RepFiltered":[45,67],)"
+ R"("Inner":{"Number":[100,200],"InnerFiltered":235}})";
+ TString my = Proto2Json(msg);
+ UNIT_ASSERT_STRINGS_EQUAL(my, expected);
+}
+
+{
+ TString expected = R"({"NotFiltered":"23",)"
+ R"("Inner":{"Number":[100,200]}})";
+ TString my = PrintWithFilter(msg, MakeFieldOptionFunctor(NProtobufJsonUt::filter_test, false));
+ UNIT_ASSERT_STRINGS_EQUAL(my, expected);
+}
+
+{
+ TString expected = R"({"OptFiltered":"1","RepFiltered":[45,67]})";
+ TString my = PrintWithFilter(msg, MakeFieldOptionFunctor(NProtobufJsonUt::filter_test));
+ UNIT_ASSERT_STRINGS_EQUAL(my, expected);
+}
+
+{
+ TString expected = R"({"OptFiltered":"1","NotFiltered":"23",)"
+ R"("Inner":{"Number":[100,200]}})";
+ TString my;
+ PrintWithFilter(msg, MakeFieldOptionFunctor(NProtobufJsonUt::export_test), *CreateJsonMapOutput(my));
+ UNIT_ASSERT_STRINGS_EQUAL(my, expected);
+}
+
+{
+ TString expected = R"({"NotFiltered":"23",)"
+ R"("Inner":{"Number":[100,200]}})";
+ auto functor = [](const NProtoBuf::Message&, const NProtoBuf::FieldDescriptor* field) {
+ return field->name() == "NotFiltered" || field->name() == "Number" || field->name() == "Inner";
+ };
+ TString my = PrintWithFilter(msg, functor);
+ UNIT_ASSERT_STRINGS_EQUAL(my, expected);
+}
+}
+
+Y_UNIT_TEST(NoUnnecessaryCopyFunctor) {
+ size_t CopyCount = 0;
+ struct TFunctorMock {
+ TFunctorMock(size_t* copyCount)
+ : CopyCount(copyCount)
+ {
+ UNIT_ASSERT(*CopyCount <= 1);
+ }
+
+ TFunctorMock(const TFunctorMock& f)
+ : CopyCount(f.CopyCount)
+ {
+ ++*CopyCount;
+ }
+
+ TFunctorMock(TFunctorMock&& f) = default;
+
+ bool operator()(const NProtoBuf::Message&, const NProtoBuf::FieldDescriptor*) const {
+ return false;
+ }
+
+ size_t* CopyCount;
+ };
+
+ TProto2JsonConfig cfg;
+ TFilteringPrinter<> printer(TFunctorMock(&CopyCount), cfg);
+ UNIT_ASSERT(CopyCount <= 1);
+}
+}
+;
diff --git a/library/cpp/protobuf/json/ut/filter_ut.proto b/library/cpp/protobuf/json/ut/filter_ut.proto
new file mode 100644
index 0000000000..29d630ade4
--- /dev/null
+++ b/library/cpp/protobuf/json/ut/filter_ut.proto
@@ -0,0 +1,20 @@
+import "google/protobuf/descriptor.proto";
+
+package NProtobufJsonUt;
+
+extend google.protobuf.FieldOptions {
+ optional bool filter_test = 58255;
+ optional bool export_test = 58256;
+}
+
+message TFilterTest {
+ optional string OptFiltered = 1 [(filter_test) = true, (export_test) = true];
+ optional string NotFiltered = 2 [(export_test) = true];
+ repeated uint64 RepFiltered = 3 [(filter_test) = true];
+
+ message TInner {
+ repeated uint32 Number = 1 [(export_test) = true];
+ optional int32 InnerFiltered = 2 [(filter_test) = true];
+ }
+ optional TInner Inner = 4 [(export_test) = true];
+}
diff --git a/library/cpp/protobuf/json/ut/inline_ut.cpp b/library/cpp/protobuf/json/ut/inline_ut.cpp
new file mode 100644
index 0000000000..c29ad32e7d
--- /dev/null
+++ b/library/cpp/protobuf/json/ut/inline_ut.cpp
@@ -0,0 +1,122 @@
+#include <library/cpp/protobuf/json/ut/inline_ut.pb.h>
+
+#include <library/cpp/protobuf/json/inline.h>
+#include <library/cpp/protobuf/json/field_option.h>
+#include <library/cpp/protobuf/json/proto2json.h>
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/generic/string.h>
+
+using namespace NProtobufJson;
+
+static NProtobufJsonUt::TInlineTest GetTestMsg() {
+ NProtobufJsonUt::TInlineTest msg;
+ msg.SetOptJson(R"({"a":1,"b":"000"})");
+ msg.SetNotJson("12{}34");
+ msg.AddRepJson("{}");
+ msg.AddRepJson("[1,2]");
+ msg.MutableInner()->AddNumber(100);
+ msg.MutableInner()->AddNumber(200);
+ msg.MutableInner()->SetInnerJson(R"({"xxx":[]})");
+ return msg;
+}
+
+Y_UNIT_TEST_SUITE(TProto2JsonInlineTest){
+ Y_UNIT_TEST(TestNormalPrint){
+ NProtobufJsonUt::TInlineTest msg = GetTestMsg();
+// normal print should output these fields as just string values
+TString expRaw = R"({"OptJson":"{\"a\":1,\"b\":\"000\"}","NotJson":"12{}34","RepJson":["{}","[1,2]"],)"
+ R"("Inner":{"Number":[100,200],"InnerJson":"{\"xxx\":[]}"}})";
+TString myRaw;
+Proto2Json(msg, myRaw);
+UNIT_ASSERT_STRINGS_EQUAL(myRaw, expRaw);
+
+myRaw = PrintInlined(msg, [](const NProtoBuf::Message&, const NProtoBuf::FieldDescriptor*) { return false; });
+UNIT_ASSERT_STRINGS_EQUAL(myRaw, expRaw); // result is the same
+}
+
+Y_UNIT_TEST(TestInliningPrinter) {
+ NProtobufJsonUt::TInlineTest msg = GetTestMsg();
+ // inlined print should output these fields as inlined json sub-objects
+ TString expInlined = R"({"OptJson":{"a":1,"b":"000"},"NotJson":"12{}34","RepJson":[{},[1,2]],)"
+ R"("Inner":{"Number":[100,200],"InnerJson":{"xxx":[]}}})";
+
+ {
+ TString myInlined = PrintInlined(msg, MakeFieldOptionFunctor(NProtobufJsonUt::inline_test));
+ UNIT_ASSERT_STRINGS_EQUAL(myInlined, expInlined);
+ }
+ {
+ auto functor = [](const NProtoBuf::Message&, const NProtoBuf::FieldDescriptor* field) {
+ return field->name() == "OptJson" || field->name() == "RepJson" || field->name() == "InnerJson";
+ };
+ TString myInlined = PrintInlined(msg, functor);
+ UNIT_ASSERT_STRINGS_EQUAL(myInlined, expInlined);
+ }
+}
+
+Y_UNIT_TEST(TestNoValues) {
+ // no values - no printing
+ NProtobufJsonUt::TInlineTest msg;
+ msg.MutableInner()->AddNumber(100);
+ msg.MutableInner()->AddNumber(200);
+
+ TString expInlined = R"({"Inner":{"Number":[100,200]}})";
+
+ TString myInlined = PrintInlined(msg, MakeFieldOptionFunctor(NProtobufJsonUt::inline_test));
+ UNIT_ASSERT_STRINGS_EQUAL(myInlined, expInlined);
+}
+
+Y_UNIT_TEST(TestMissingKeyModeNull) {
+ NProtobufJsonUt::TInlineTest msg;
+ msg.MutableInner()->AddNumber(100);
+ msg.MutableInner()->AddNumber(200);
+
+ TString expInlined = R"({"OptJson":null,"NotJson":null,"RepJson":null,"Inner":{"Number":[100,200],"InnerJson":null}})";
+
+ TProto2JsonConfig cfg;
+ cfg.SetMissingSingleKeyMode(TProto2JsonConfig::MissingKeyNull).SetMissingRepeatedKeyMode(TProto2JsonConfig::MissingKeyNull);
+ TString myInlined = PrintInlined(msg, MakeFieldOptionFunctor(NProtobufJsonUt::inline_test), cfg);
+ UNIT_ASSERT_STRINGS_EQUAL(myInlined, expInlined);
+}
+
+Y_UNIT_TEST(TestMissingKeyModeDefault) {
+ NProtobufJsonUt::TInlineTestDefaultValues msg;
+
+ TString expInlined = R"({"OptJson":{"default":1},"Number":0,"RepJson":[],"Inner":{"OptJson":{"default":2}}})";
+
+ TProto2JsonConfig cfg;
+ cfg.SetMissingSingleKeyMode(TProto2JsonConfig::MissingKeyDefault).SetMissingRepeatedKeyMode(TProto2JsonConfig::MissingKeyDefault);
+ TString myInlined = PrintInlined(msg, MakeFieldOptionFunctor(NProtobufJsonUt::inline_test), cfg);
+ UNIT_ASSERT_STRINGS_EQUAL(myInlined, expInlined);
+}
+
+Y_UNIT_TEST(NoUnnecessaryCopyFunctor) {
+ size_t CopyCount = 0;
+ struct TFunctorMock {
+ TFunctorMock(size_t* copyCount)
+ : CopyCount(copyCount)
+ {
+ UNIT_ASSERT(*CopyCount <= 1);
+ }
+
+ TFunctorMock(const TFunctorMock& f)
+ : CopyCount(f.CopyCount)
+ {
+ ++*CopyCount;
+ }
+
+ TFunctorMock(TFunctorMock&& f) = default;
+
+ bool operator()(const NProtoBuf::Message&, const NProtoBuf::FieldDescriptor*) const {
+ return false;
+ }
+
+ size_t* CopyCount;
+ };
+
+ TProto2JsonConfig cfg;
+ TInliningPrinter<> printer(TFunctorMock(&CopyCount), cfg);
+ UNIT_ASSERT(CopyCount <= 1);
+}
+}
+;
diff --git a/library/cpp/protobuf/json/ut/inline_ut.proto b/library/cpp/protobuf/json/ut/inline_ut.proto
new file mode 100644
index 0000000000..76bd10232d
--- /dev/null
+++ b/library/cpp/protobuf/json/ut/inline_ut.proto
@@ -0,0 +1,29 @@
+import "google/protobuf/descriptor.proto";
+
+package NProtobufJsonUt;
+
+extend google.protobuf.FieldOptions {
+ optional bool inline_test = 58253;
+}
+
+message TInlineTest {
+ optional string OptJson = 1 [(inline_test) = true];
+ optional string NotJson = 2;
+ repeated string RepJson = 3 [(inline_test) = true];
+
+ message TInner {
+ repeated uint32 Number = 1;
+ optional string InnerJson = 2 [(inline_test) = true];
+ }
+ optional TInner Inner = 4;
+}
+
+message TInlineTestDefaultValues {
+ optional string OptJson = 1 [(inline_test) = true, default = "{\"default\":1}"];
+ optional uint32 Number = 2;
+ repeated string RepJson = 3 [(inline_test) = true];
+ message TInner {
+ optional string OptJson = 1 [(inline_test) = true, default = "{\"default\":2}"];
+ }
+ optional TInner Inner = 4;
+}
diff --git a/library/cpp/protobuf/json/ut/json.h b/library/cpp/protobuf/json/ut/json.h
new file mode 100644
index 0000000000..c1f108e6e4
--- /dev/null
+++ b/library/cpp/protobuf/json/ut/json.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <library/cpp/protobuf/json/ut/test.pb.h>
+
+#include <library/cpp/json/json_value.h>
+
+#include <cstdarg>
+
+#include <util/generic/hash_set.h>
+#include <util/generic/string.h>
+
+#include <util/system/defaults.h>
+
+namespace NProtobufJsonTest {
+ inline NJson::TJsonValue
+ CreateFlatJson(const THashSet<TString>& skippedKeys = THashSet<TString>()) {
+ NJson::TJsonValue json;
+
+#define DEFINE_FIELD(name, value) \
+ if (skippedKeys.find(#name) == skippedKeys.end()) \
+ json.InsertValue(#name, value);
+#include "fields.incl"
+#undef DEFINE_FIELD
+
+ return json;
+ }
+
+ inline NJson::TJsonValue
+ CreateRepeatedFlatJson(const THashSet<TString>& skippedKeys = THashSet<TString>()) {
+ NJson::TJsonValue json;
+
+#define DEFINE_REPEATED_FIELD(name, type, ...) \
+ if (skippedKeys.find(#name) == skippedKeys.end()) { \
+ type values[] = {__VA_ARGS__}; \
+ NJson::TJsonValue array(NJson::JSON_ARRAY); \
+ for (size_t i = 0, end = Y_ARRAY_SIZE(values); i < end; ++i) { \
+ array.AppendValue(values[i]); \
+ } \
+ json.InsertValue(#name, array); \
+ }
+#include "repeated_fields.incl"
+#undef DEFINE_REPEATED_FIELD
+
+ return json;
+ }
+
+ inline NJson::TJsonValue
+ CreateCompositeJson(const THashSet<TString>& skippedKeys = THashSet<TString>()) {
+ const NJson::TJsonValue& part = CreateFlatJson(skippedKeys);
+ NJson::TJsonValue json;
+ json.InsertValue("Part", part);
+
+ return json;
+ }
+
+#define UNIT_ASSERT_JSONS_EQUAL(lhs, rhs) \
+ if (lhs != rhs) { \
+ UNIT_ASSERT_STRINGS_EQUAL(lhs.GetStringRobust(), rhs.GetStringRobust()); \
+ }
+
+#define UNIT_ASSERT_JSON_STRINGS_EQUAL(lhs, rhs) \
+ if (lhs != rhs) { \
+ NJson::TJsonValue _lhs_json, _rhs_json; \
+ UNIT_ASSERT(NJson::ReadJsonTree(lhs, &_lhs_json)); \
+ UNIT_ASSERT(NJson::ReadJsonTree(rhs, &_rhs_json)); \
+ UNIT_ASSERT_JSONS_EQUAL(_lhs_json, _rhs_json); \
+ }
+
+}
diff --git a/library/cpp/protobuf/json/ut/json2proto_ut.cpp b/library/cpp/protobuf/json/ut/json2proto_ut.cpp
new file mode 100644
index 0000000000..0dfe57bc7a
--- /dev/null
+++ b/library/cpp/protobuf/json/ut/json2proto_ut.cpp
@@ -0,0 +1,1147 @@
+#include "json.h"
+#include "proto.h"
+#include "proto2json.h"
+
+#include <library/cpp/protobuf/json/ut/test.pb.h>
+
+#include <library/cpp/json/json_value.h>
+#include <library/cpp/json/json_reader.h>
+#include <library/cpp/json/json_writer.h>
+
+#include <library/cpp/protobuf/json/json2proto.h>
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/generic/hash_set.h>
+#include <util/generic/string.h>
+#include <util/generic/ylimits.h>
+#include <util/stream/str.h>
+#include <util/string/cast.h>
+#include <util/system/defaults.h>
+#include <util/system/yassert.h>
+
+using namespace NProtobufJson;
+using namespace NProtobufJsonTest;
+
+namespace google {
+ namespace protobuf {
+ namespace internal {
+ void MapTestForceDeterministic() {
+ google::protobuf::io::CodedOutputStream::SetDefaultSerializationDeterministic();
+ }
+ }
+ } // namespace protobuf
+}
+
+namespace {
+ class TInit {
+ public:
+ TInit() {
+ ::google::protobuf::internal::MapTestForceDeterministic();
+ }
+ } Init;
+
+ template <typename T>
+ TString ConvertToString(T value) {
+ return ToString(value);
+ }
+
+ // default ToString<double>() implementation loses precision
+ TString ConvertToString(double value) {
+ return FloatToString(value);
+ }
+
+ TString JsonValueToString(const NJson::TJsonValue& json) {
+ NJsonWriter::TBuf buf(NJsonWriter::HEM_UNSAFE);
+ return buf.WriteJsonValue(&json).Str();
+ }
+
+ void TestComplexMapAsObject(std::function<void(TComplexMapType&)>&& init, const TString& json, const TJson2ProtoConfig& config = TJson2ProtoConfig().SetMapAsObject(true)) {
+ TComplexMapType modelProto;
+
+ init(modelProto);
+
+ TString modelStr(json);
+
+ TComplexMapType proto;
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TComplexMapType>(modelStr, config));
+
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+ }
+}
+
+Y_UNIT_TEST_SUITE(TJson2ProtoTest) {
+ Y_UNIT_TEST(TestFlatOptional){
+ {const NJson::TJsonValue& json = CreateFlatJson();
+ TFlatOptional proto;
+ Json2Proto(json, proto);
+ TFlatOptional modelProto;
+ FillFlatProto(&modelProto);
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+}
+
+ // Try to skip each field
+#define DEFINE_FIELD(name, value) \
+ { \
+ THashSet<TString> skippedField; \
+ skippedField.insert(#name); \
+ const NJson::TJsonValue& json = CreateFlatJson(skippedField); \
+ TFlatOptional proto; \
+ Json2Proto(json, proto); \
+ TFlatOptional modelProto; \
+ FillFlatProto(&modelProto, skippedField); \
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); \
+ }
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+} // TestFlatOptional
+
+Y_UNIT_TEST(TestFlatRequired){
+ {const NJson::TJsonValue& json = CreateFlatJson();
+TFlatRequired proto;
+Json2Proto(json, proto);
+TFlatRequired modelProto;
+FillFlatProto(&modelProto);
+UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+}
+
+// Try to skip each field
+#define DEFINE_FIELD(name, value) \
+ { \
+ THashSet<TString> skippedField; \
+ skippedField.insert(#name); \
+ const NJson::TJsonValue& json = CreateFlatJson(skippedField); \
+ TFlatRequired proto; \
+ UNIT_ASSERT_EXCEPTION(Json2Proto(json, proto), yexception); \
+ }
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+} // TestFlatRequired
+
+Y_UNIT_TEST(TestNameGenerator) {
+ TJson2ProtoConfig cfg;
+ cfg.SetNameGenerator([](const NProtoBuf::FieldDescriptor&) { return "42"; });
+
+ TNameGeneratorType proto;
+ Json2Proto(TStringBuf(R"({"42":42})"), proto, cfg);
+
+ TNameGeneratorType expected;
+ expected.SetField(42);
+
+ UNIT_ASSERT_PROTOS_EQUAL(expected, proto);
+}
+
+Y_UNIT_TEST(TestFlatNoCheckRequired) {
+ {
+ const NJson::TJsonValue& json = CreateFlatJson();
+ TFlatRequired proto;
+ Json2Proto(json, proto);
+ TFlatRequired modelProto;
+ FillFlatProto(&modelProto);
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+ }
+
+ TJson2ProtoConfig cfg;
+ cfg.CheckRequiredFields = false;
+
+ // Try to skip each field
+#define DEFINE_FIELD(name, value) \
+ { \
+ THashSet<TString> skippedField; \
+ skippedField.insert(#name); \
+ const NJson::TJsonValue& json = CreateFlatJson(skippedField); \
+ TFlatRequired proto; \
+ UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto, cfg)); \
+ }
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+} // TestFlatNoCheckRequired
+
+Y_UNIT_TEST(TestFlatRepeated){
+ {const NJson::TJsonValue& json = CreateRepeatedFlatJson();
+TFlatRepeated proto;
+Json2Proto(json, proto);
+TFlatRepeated modelProto;
+FillRepeatedProto(&modelProto);
+UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+}
+
+// Try to skip each field
+#define DEFINE_REPEATED_FIELD(name, ...) \
+ { \
+ THashSet<TString> skippedField; \
+ skippedField.insert(#name); \
+ const NJson::TJsonValue& json = CreateRepeatedFlatJson(skippedField); \
+ TFlatRepeated proto; \
+ Json2Proto(json, proto); \
+ TFlatRepeated modelProto; \
+ FillRepeatedProto(&modelProto, skippedField); \
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); \
+ }
+#include <library/cpp/protobuf/json/ut/repeated_fields.incl>
+#undef DEFINE_REPEATED_FIELD
+} // TestFlatRepeated
+
+Y_UNIT_TEST(TestCompositeOptional){
+ {const NJson::TJsonValue& json = CreateCompositeJson();
+TCompositeOptional proto;
+Json2Proto(json, proto);
+TCompositeOptional modelProto;
+FillCompositeProto(&modelProto);
+UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+}
+
+// Try to skip each field
+#define DEFINE_FIELD(name, value) \
+ { \
+ THashSet<TString> skippedField; \
+ skippedField.insert(#name); \
+ const NJson::TJsonValue& json = CreateCompositeJson(skippedField); \
+ TCompositeOptional proto; \
+ Json2Proto(json, proto); \
+ TCompositeOptional modelProto; \
+ FillCompositeProto(&modelProto, skippedField); \
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); \
+ }
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+} // TestCompositeOptional
+
+Y_UNIT_TEST(TestCompositeOptionalStringBuf){
+ {NJson::TJsonValue json = CreateCompositeJson();
+json["Part"]["Double"] = 42.5;
+TCompositeOptional proto;
+Json2Proto(JsonValueToString(json), proto);
+TCompositeOptional modelProto;
+FillCompositeProto(&modelProto);
+modelProto.MutablePart()->SetDouble(42.5);
+UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+}
+
+// Try to skip each field
+#define DEFINE_FIELD(name, value) \
+ { \
+ THashSet<TString> skippedField; \
+ skippedField.insert(#name); \
+ NJson::TJsonValue json = CreateCompositeJson(skippedField); \
+ if (json["Part"].Has("Double")) { \
+ json["Part"]["Double"] = 42.5; \
+ } \
+ TCompositeOptional proto; \
+ Json2Proto(JsonValueToString(json), proto); \
+ TCompositeOptional modelProto; \
+ FillCompositeProto(&modelProto, skippedField); \
+ if (modelProto.GetPart().HasDouble()) { \
+ modelProto.MutablePart()->SetDouble(42.5); \
+ } \
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); \
+ }
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+} // TestCompositeOptionalStringBuf
+
+Y_UNIT_TEST(TestCompositeRequired) {
+ {
+ const NJson::TJsonValue& json = CreateCompositeJson();
+ TCompositeRequired proto;
+ Json2Proto(json, proto);
+ TCompositeRequired modelProto;
+ FillCompositeProto(&modelProto);
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+ }
+
+ {
+ NJson::TJsonValue json;
+ TCompositeRequired proto;
+ UNIT_ASSERT_EXCEPTION(Json2Proto(json, proto), yexception);
+ }
+} // TestCompositeRequired
+
+Y_UNIT_TEST(TestCompositeRepeated) {
+ {
+ NJson::TJsonValue json;
+ NJson::TJsonValue array;
+ array.AppendValue(CreateFlatJson());
+ json.InsertValue("Part", array);
+
+ TCompositeRepeated proto;
+ Json2Proto(json, proto);
+
+ TFlatOptional partModelProto;
+ FillFlatProto(&partModelProto);
+ TCompositeRepeated modelProto;
+ modelProto.AddPart()->CopyFrom(partModelProto);
+
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+ }
+
+ {
+ // Array of messages with each field skipped
+ TCompositeRepeated modelProto;
+ NJson::TJsonValue array;
+
+#define DEFINE_REPEATED_FIELD(name, ...) \
+ { \
+ THashSet<TString> skippedField; \
+ skippedField.insert(#name); \
+ TFlatOptional partModelProto; \
+ FillFlatProto(&partModelProto, skippedField); \
+ modelProto.AddPart()->CopyFrom(partModelProto); \
+ array.AppendValue(CreateFlatJson(skippedField)); \
+ }
+#include <library/cpp/protobuf/json/ut/repeated_fields.incl>
+#undef DEFINE_REPEATED_FIELD
+
+ NJson::TJsonValue json;
+ json.InsertValue("Part", array);
+
+ TCompositeRepeated proto;
+ Json2Proto(json, proto);
+
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+ }
+} // TestCompositeRepeated
+
+Y_UNIT_TEST(TestInvalidEnum) {
+ {
+ NJson::TJsonValue json;
+ json.InsertValue("Enum", "E_100");
+ TFlatOptional proto;
+ UNIT_ASSERT_EXCEPTION(Json2Proto(json, proto), yexception);
+ }
+
+ {
+ NJson::TJsonValue json;
+ json.InsertValue("Enum", 100);
+ TFlatOptional proto;
+ UNIT_ASSERT_EXCEPTION(Json2Proto(json, proto), yexception);
+ }
+}
+
+Y_UNIT_TEST(TestFieldNameMode) {
+ // Original case 1
+ {
+ TString modelStr(R"_({"String":"value"})_");
+
+ TFlatOptional proto;
+ TJson2ProtoConfig config;
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config));
+ UNIT_ASSERT(proto.GetString() == "value");
+ }
+
+ // Original case 2
+ {
+ TString modelStr(R"_({"String":"value"})_");
+
+ TFlatOptional proto;
+ TJson2ProtoConfig config;
+ config.FieldNameMode = TJson2ProtoConfig::FieldNameOriginalCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config));
+ UNIT_ASSERT(proto.GetString() == "value");
+ }
+
+ // Lowercase
+ {
+ TString modelStr(R"_({"string":"value"})_");
+
+ TFlatOptional proto;
+ TJson2ProtoConfig config;
+ config.FieldNameMode = TJson2ProtoConfig::FieldNameLowerCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config));
+ UNIT_ASSERT(proto.GetString() == "value");
+ }
+
+ // Uppercase
+ {
+ TString modelStr(R"_({"STRING":"value"})_");
+
+ TFlatOptional proto;
+ TJson2ProtoConfig config;
+ config.FieldNameMode = TJson2ProtoConfig::FieldNameUpperCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config));
+ UNIT_ASSERT(proto.GetString() == "value");
+ }
+
+ // Camelcase
+ {
+ TString modelStr(R"_({"string":"value"})_");
+
+ TFlatOptional proto;
+ TJson2ProtoConfig config;
+ config.FieldNameMode = TJson2ProtoConfig::FieldNameCamelCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config));
+ UNIT_ASSERT(proto.GetString() == "value");
+ }
+ {
+ TString modelStr(R"_({"oneString":"value"})_");
+
+ TFlatOptional proto;
+ TJson2ProtoConfig config;
+ config.FieldNameMode = TJson2ProtoConfig::FieldNameCamelCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config));
+ UNIT_ASSERT(proto.GetOneString() == "value");
+ }
+ {
+ TString modelStr(R"_({"oneTwoString":"value"})_");
+
+ TFlatOptional proto;
+ TJson2ProtoConfig config;
+ config.FieldNameMode = TJson2ProtoConfig::FieldNameCamelCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config));
+ UNIT_ASSERT(proto.GetOneTwoString() == "value");
+ }
+
+ // snake_case
+ {
+ TString modelStr(R"_({"string":"value"})_");
+
+ TFlatOptional proto;
+ TJson2ProtoConfig config;
+ config.FieldNameMode = TJson2ProtoConfig::FieldNameSnakeCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config));
+ UNIT_ASSERT(proto.GetString() == "value");
+ }
+ {
+ TString modelStr(R"_({"one_string":"value"})_");
+
+ TFlatOptional proto;
+ TJson2ProtoConfig config;
+ config.FieldNameMode = TJson2ProtoConfig::FieldNameSnakeCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config));
+ UNIT_ASSERT(proto.GetOneString() == "value");
+ }
+ {
+ TString modelStr(R"_({"one_two_string":"value"})_");
+
+ TFlatOptional proto;
+ TJson2ProtoConfig config;
+ config.FieldNameMode = TJson2ProtoConfig::FieldNameSnakeCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config));
+ UNIT_ASSERT(proto.GetOneTwoString() == "value");
+ }
+
+ // Original case, repeated
+ {
+ TString modelStr(R"_({"I32":[1,2]})_");
+
+ TFlatRepeated proto;
+ TJson2ProtoConfig config;
+ config.FieldNameMode = TJson2ProtoConfig::FieldNameOriginalCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatRepeated>(modelStr, config));
+ UNIT_ASSERT(proto.I32Size() == 2);
+ UNIT_ASSERT(proto.GetI32(0) == 1);
+ UNIT_ASSERT(proto.GetI32(1) == 2);
+ }
+
+ // Lower case, repeated
+ {
+ TString modelStr(R"_({"i32":[1,2]})_");
+
+ TFlatRepeated proto;
+ TJson2ProtoConfig config;
+ config.FieldNameMode = TJson2ProtoConfig::FieldNameLowerCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatRepeated>(modelStr, config));
+ UNIT_ASSERT(proto.I32Size() == 2);
+ UNIT_ASSERT(proto.GetI32(0) == 1);
+ UNIT_ASSERT(proto.GetI32(1) == 2);
+ }
+
+ // UseJsonName
+ {
+ // FIXME(CONTRIB-139): since protobuf 3.1, Def_upper json name is
+ // "DefUpper", but until kernel/ugc/schema and yweb/yasap/pdb are
+ // updated, library/cpp/protobuf/json preserves compatibility with
+ // protobuf 3.0 by lowercasing default names, making it "defUpper".
+ TString modelStr(R"_({"My-Upper":1,"my-lower":2,"defUpper":3,"defLower":4})_");
+
+ TWithJsonName proto;
+ TJson2ProtoConfig config;
+ config.SetUseJsonName(true);
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TWithJsonName>(modelStr, config));
+ UNIT_ASSERT_EQUAL(proto.Getmy_upper(), 1);
+ UNIT_ASSERT_EQUAL(proto.GetMy_lower(), 2);
+ UNIT_ASSERT_EQUAL(proto.GetDef_upper(), 3);
+ UNIT_ASSERT_EQUAL(proto.Getdef_lower(), 4);
+ }
+
+ // FieldNameMode with UseJsonName
+ {
+ TJson2ProtoConfig config;
+ config.SetFieldNameMode(TJson2ProtoConfig::FieldNameLowerCase);
+ UNIT_ASSERT_EXCEPTION_CONTAINS(
+ config.SetUseJsonName(true), yexception, "mutually exclusive");
+ }
+ {
+ TJson2ProtoConfig config;
+ config.SetUseJsonName(true);
+ UNIT_ASSERT_EXCEPTION_CONTAINS(
+ config.SetFieldNameMode(TJson2ProtoConfig::FieldNameLowerCase), yexception, "mutually exclusive");
+ }
+} // TestFieldNameMode
+
+class TStringTransform: public IStringTransform {
+public:
+ int GetType() const override {
+ return 0;
+ }
+ void Transform(TString& str) const override {
+ str = "transformed_any";
+ }
+};
+
+class TBytesTransform: public IStringTransform {
+public:
+ int GetType() const override {
+ return 0;
+ }
+ void Transform(TString&) const override {
+ }
+ void TransformBytes(TString& str) const override {
+ str = "transformed_bytes";
+ }
+};
+
+Y_UNIT_TEST(TestInvalidJson) {
+ NJson::TJsonValue val{"bad value"};
+ TFlatOptional proto;
+ UNIT_ASSERT_EXCEPTION(Json2Proto(val, proto), yexception);
+}
+
+Y_UNIT_TEST(TestInvalidRepeatedFieldWithMapAsObject) {
+ TCompositeRepeated proto;
+ TJson2ProtoConfig config;
+ config.MapAsObject = true;
+ UNIT_ASSERT_EXCEPTION(Json2Proto(TStringBuf(R"({"Part":{"Boo":{}}})"), proto, config), yexception);
+}
+
+Y_UNIT_TEST(TestStringTransforms) {
+ // Check that strings and bytes are transformed
+ {
+ TString modelStr(R"_({"String":"value_str", "Bytes": "value_bytes"})_");
+
+ TFlatOptional proto;
+ TJson2ProtoConfig config;
+ config.AddStringTransform(new TStringTransform);
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config));
+ UNIT_ASSERT(proto.GetString() == "transformed_any");
+ UNIT_ASSERT(proto.GetBytes() == "transformed_any");
+ }
+
+ // Check that bytes are transformed, strings are left intact
+ {
+ TString modelStr(R"_({"String":"value_str", "Bytes": "value_bytes"})_");
+
+ TFlatOptional proto;
+ TJson2ProtoConfig config;
+ config.AddStringTransform(new TBytesTransform);
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config));
+ UNIT_ASSERT(proto.GetString() == "value_str");
+ UNIT_ASSERT(proto.GetBytes() == "transformed_bytes");
+ }
+
+ // Check that repeated bytes are transformed, repeated strings are left intact
+ {
+ TString modelStr(R"_({"String":["value_str", "str2"], "Bytes": ["value_bytes", "bytes2"]})_");
+
+ TFlatRepeated proto;
+ TJson2ProtoConfig config;
+ config.AddStringTransform(new TBytesTransform);
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatRepeated>(modelStr, config));
+ UNIT_ASSERT(proto.StringSize() == 2);
+ UNIT_ASSERT(proto.GetString(0) == "value_str");
+ UNIT_ASSERT(proto.GetString(1) == "str2");
+ UNIT_ASSERT(proto.BytesSize() == 2);
+ UNIT_ASSERT(proto.GetBytes(0) == "transformed_bytes");
+ UNIT_ASSERT(proto.GetBytes(1) == "transformed_bytes");
+ }
+
+ // Check that bytes are transformed, strings are left intact in composed messages
+ {
+ TString modelStr(R"_({"Part": {"String":"value_str", "Bytes": "value_bytes"}})_");
+
+ TCompositeOptional proto;
+ TJson2ProtoConfig config;
+ config.AddStringTransform(new TBytesTransform);
+
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TCompositeOptional>(modelStr, config));
+ UNIT_ASSERT(proto.GetPart().GetString() == "value_str");
+ UNIT_ASSERT(proto.GetPart().GetBytes() == "transformed_bytes");
+ }
+} // TestStringTransforms
+
+Y_UNIT_TEST(TestCastFromString) {
+ // single fields
+ {
+ NJson::TJsonValue json;
+#define DEFINE_FIELD(name, value) \
+ json.InsertValue(#name, ConvertToString(value));
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+
+ TFlatOptional proto;
+ UNIT_ASSERT_EXCEPTION_CONTAINS(Json2Proto(json, proto), yexception, "Invalid type");
+
+ TJson2ProtoConfig config;
+ config.SetCastFromString(true);
+ Json2Proto(json, proto, config);
+
+ TFlatOptional modelProto;
+ FillFlatProto(&modelProto);
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+ }
+
+ // repeated fields
+ {
+ NJson::TJsonValue json;
+#define DEFINE_REPEATED_FIELD(name, type, ...) \
+ { \
+ type values[] = {__VA_ARGS__}; \
+ NJson::TJsonValue array(NJson::JSON_ARRAY); \
+ for (size_t i = 0, end = Y_ARRAY_SIZE(values); i < end; ++i) { \
+ array.AppendValue(ConvertToString(values[i])); \
+ } \
+ json.InsertValue(#name, array); \
+ }
+#include <library/cpp/protobuf/json/ut/repeated_fields.incl>
+#undef DEFINE_REPEATED_FIELD
+
+ TFlatRepeated proto;
+ UNIT_ASSERT_EXCEPTION_CONTAINS(Json2Proto(json, proto), yexception, "Invalid type");
+
+ TJson2ProtoConfig config;
+ config.SetCastFromString(true);
+ Json2Proto(json, proto, config);
+
+ TFlatRepeated modelProto;
+ FillRepeatedProto(&modelProto);
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+ }
+} // TestCastFromString
+
+Y_UNIT_TEST(TestMap) {
+ TMapType modelProto;
+
+ auto& items = *modelProto.MutableItems();
+ items["key1"] = "value1";
+ items["key2"] = "value2";
+ items["key3"] = "value3";
+
+ TString modelStr(R"_({"Items":[{"key":"key3","value":"value3"},{"key":"key2","value":"value2"},{"key":"key1","value":"value1"}]})_");
+
+ TJson2ProtoConfig config;
+ TMapType proto;
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TMapType>(modelStr, config));
+
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+} // TestMap
+
+Y_UNIT_TEST(TestCastRobust) {
+ NJson::TJsonValue json;
+ json["I32"] = "5";
+ json["Bool"] = 1;
+ json["String"] = 6;
+ json["Double"] = 8;
+ TFlatOptional proto;
+ UNIT_ASSERT_EXCEPTION_CONTAINS(Json2Proto(json, proto), yexception, "Invalid type");
+
+ TJson2ProtoConfig config;
+ config.SetCastRobust(true);
+ Json2Proto(json, proto, config);
+
+ TFlatOptional expected;
+ expected.SetI32(5);
+ expected.SetBool(true);
+ expected.SetString("6");
+ expected.SetDouble(8);
+ UNIT_ASSERT_PROTOS_EQUAL(proto, expected);
+}
+
+Y_UNIT_TEST(TestVectorizeScalars) {
+ NJson::TJsonValue json;
+#define DEFINE_FIELD(name, value) \
+ json.InsertValue(#name, value);
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+
+ TFlatRepeated proto;
+ TJson2ProtoConfig config;
+ config.SetVectorizeScalars(true);
+ Json2Proto(json, proto, config);
+
+#define DEFINE_FIELD(name, value) \
+ UNIT_ASSERT_VALUES_EQUAL(proto.Get ## name(0), value);
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+}
+
+Y_UNIT_TEST(TestValueVectorizer) {
+ {
+ // No ValueVectorizer
+ NJson::TJsonValue json;
+ json["RepeatedString"] = "123";
+ TJson2ProtoConfig config;
+ TSingleRepeatedString expected;
+ UNIT_ASSERT_EXCEPTION(Json2Proto(json, expected, config), yexception);
+ }
+ {
+ // ValueVectorizer replace original value by array
+ NJson::TJsonValue json;
+ json["RepeatedString"] = "123";
+ TJson2ProtoConfig config;
+
+ TSingleRepeatedString expected;
+ expected.AddRepeatedString("4");
+ expected.AddRepeatedString("5");
+ expected.AddRepeatedString("6");
+
+ config.ValueVectorizer = [](const NJson::TJsonValue& val) -> NJson::TJsonValue::TArray {
+ Y_UNUSED(val);
+ return {NJson::TJsonValue("4"), NJson::TJsonValue("5"), NJson::TJsonValue("6")};
+ };
+ TSingleRepeatedString actual;
+ Json2Proto(json, actual, config);
+ UNIT_ASSERT_PROTOS_EQUAL(expected, actual);
+ }
+ {
+ // ValueVectorizer replace original value by array and cast
+ NJson::TJsonValue json;
+ json["RepeatedInt"] = 123;
+ TJson2ProtoConfig config;
+
+ TSingleRepeatedInt expected;
+ expected.AddRepeatedInt(4);
+ expected.AddRepeatedInt(5);
+ expected.AddRepeatedInt(6);
+
+ config.ValueVectorizer = [](const NJson::TJsonValue& val) -> NJson::TJsonValue::TArray {
+ Y_UNUSED(val);
+ return {NJson::TJsonValue("4"), NJson::TJsonValue(5), NJson::TJsonValue("6")};
+ };
+ config.CastFromString = true;
+
+ TSingleRepeatedInt actual;
+ Json2Proto(json, actual, config);
+ UNIT_ASSERT_PROTOS_EQUAL(expected, actual);
+ }
+}
+
+Y_UNIT_TEST(TestMapAsObject) {
+ TMapType modelProto;
+
+ auto& items = *modelProto.MutableItems();
+ items["key1"] = "value1";
+ items["key2"] = "value2";
+ items["key3"] = "value3";
+
+ TString modelStr(R"_({"Items":{"key1":"value1","key2":"value2","key3":"value3"}})_");
+
+ TJson2ProtoConfig config;
+ config.MapAsObject = true;
+ TMapType proto;
+ UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TMapType>(modelStr, config));
+
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+} // TestMapAsObject
+
+Y_UNIT_TEST(TestComplexMapAsObject_I32) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableI32();
+ items[1] = 1;
+ items[-2] = -2;
+ items[3] = 3;
+ },
+ R"_({"I32":{"1":1,"-2":-2,"3":3}})_");
+} // TestComplexMapAsObject_I32
+
+Y_UNIT_TEST(TestComplexMapAsObject_I64) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableI64();
+ items[2147483649L] = 2147483649L;
+ items[-2147483650L] = -2147483650L;
+ items[2147483651L] = 2147483651L;
+ },
+ R"_({"I64":{"2147483649":2147483649,"-2147483650":-2147483650,"2147483651":2147483651}})_");
+} // TestComplexMapAsObject_I64
+
+Y_UNIT_TEST(TestComplexMapAsObject_UI32) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableUI32();
+ items[1073741825U] = 1073741825U;
+ items[1073741826U] = 1073741826U;
+ items[1073741827U] = 1073741827U;
+ },
+ R"_({"UI32":{"1073741825":1073741825,"1073741826":1073741826,"1073741827":1073741827}})_");
+} // TestComplexMapAsObject_UI32
+
+Y_UNIT_TEST(TestComplexMapAsObject_UI64) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableUI64();
+ items[9223372036854775809UL] = 9223372036854775809UL;
+ items[9223372036854775810UL] = 9223372036854775810UL;
+ items[9223372036854775811UL] = 9223372036854775811UL;
+ },
+ R"_({"UI64":{"9223372036854775809":9223372036854775809,"9223372036854775810":9223372036854775810,"9223372036854775811":9223372036854775811}})_");
+} // TestComplexMapAsObject_UI64
+
+Y_UNIT_TEST(TestComplexMapAsObject_SI32) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableSI32();
+ items[1] = 1;
+ items[-2] = -2;
+ items[3] = 3;
+ },
+ R"_({"SI32":{"1":1,"-2":-2,"3":3}})_");
+} // TestComplexMapAsObject_SI32
+
+Y_UNIT_TEST(TestComplexMapAsObject_SI64) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableSI64();
+ items[2147483649L] = 2147483649L;
+ items[-2147483650L] = -2147483650L;
+ items[2147483651L] = 2147483651L;
+ },
+ R"_({"SI64":{"2147483649":2147483649,"-2147483650":-2147483650,"2147483651":2147483651}})_");
+} // TestComplexMapAsObject_SI64
+
+Y_UNIT_TEST(TestComplexMapAsObject_FI32) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableFI32();
+ items[1073741825U] = 1073741825U;
+ items[1073741826U] = 1073741826U;
+ items[1073741827U] = 1073741827U;
+ },
+ R"_({"FI32":{"1073741825":1073741825,"1073741826":1073741826,"1073741827":1073741827}})_");
+} // TestComplexMapAsObject_FI32
+
+Y_UNIT_TEST(TestComplexMapAsObject_FI64) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableFI64();
+ items[9223372036854775809UL] = 9223372036854775809UL;
+ items[9223372036854775810UL] = 9223372036854775810UL;
+ items[9223372036854775811UL] = 9223372036854775811UL;
+ },
+ R"_({"FI64":{"9223372036854775809":9223372036854775809,"9223372036854775810":9223372036854775810,"9223372036854775811":9223372036854775811}})_");
+} // TestComplexMapAsObject_FI64
+
+Y_UNIT_TEST(TestComplexMapAsObject_SFI32) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableSFI32();
+ items[1] = 1;
+ items[-2] = -2;
+ items[3] = 3;
+ },
+ R"_({"SFI32":{"1":1,"-2":-2,"3":3}})_");
+} // TestComplexMapAsObject_SFI32
+
+Y_UNIT_TEST(TestComplexMapAsObject_SFI64) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableSFI64();
+ items[2147483649L] = 2147483649L;
+ items[-2147483650L] = -2147483650L;
+ items[2147483651L] = 2147483651L;
+ },
+ R"_({"SFI64":{"2147483649":2147483649,"-2147483650":-2147483650,"2147483651":2147483651}})_");
+} // TestComplexMapAsObject_SFI64
+
+Y_UNIT_TEST(TestComplexMapAsObject_Bool) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableBool();
+ items[true] = true;
+ items[false] = false;
+ },
+ R"_({"Bool":{"true":true,"false":false}})_");
+} // TestComplexMapAsObject_Bool
+
+Y_UNIT_TEST(TestComplexMapAsObject_String) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableString();
+ items["key1"] = "value1";
+ items["key2"] = "value2";
+ items["key3"] = "value3";
+ items[""] = "value4";
+ },
+ R"_({"String":{"key1":"value1","key2":"value2","key3":"value3","":"value4"}})_");
+} // TestComplexMapAsObject_String
+
+Y_UNIT_TEST(TestComplexMapAsObject_Enum) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableEnum();
+ items["key1"] = EEnum::E_1;
+ items["key2"] = EEnum::E_2;
+ items["key3"] = EEnum::E_3;
+ },
+ R"_({"Enum":{"key1":1,"key2":2,"key3":3}})_");
+} // TestComplexMapAsObject_Enum
+
+Y_UNIT_TEST(TestComplexMapAsObject_EnumString) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableEnum();
+ items["key1"] = EEnum::E_1;
+ items["key2"] = EEnum::E_2;
+ items["key3"] = EEnum::E_3;
+ },
+ R"_({"Enum":{"key1":"E_1","key2":"E_2","key3":"E_3"}})_");
+} // TestComplexMapAsObject_EnumString
+
+Y_UNIT_TEST(TestComplexMapAsObject_EnumStringCaseInsensetive) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableEnum();
+ items["key1"] = EEnum::E_1;
+ items["key2"] = EEnum::E_2;
+ items["key3"] = EEnum::E_3;
+ },
+ R"_({"Enum":{"key1":"e_1","key2":"E_2","key3":"e_3"}})_",
+ TJson2ProtoConfig()
+ .SetMapAsObject(true)
+ .SetEnumValueMode(NProtobufJson::TJson2ProtoConfig::EnumCaseInsensetive)
+ );
+} // TestComplexMapAsObject_EnumStringCaseInsensetive
+
+Y_UNIT_TEST(TestComplexMapAsObject_EnumStringSnakeCaseInsensitive) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableEnum();
+ items["key1"] = EEnum::E_1;
+ items["key2"] = EEnum::E_2;
+ items["key3"] = EEnum::E_3;
+ },
+ R"_({"Enum":{"key1":"e1","key2":"_E_2_","key3":"e_3"}})_",
+ TJson2ProtoConfig()
+ .SetMapAsObject(true)
+ .SetEnumValueMode(NProtobufJson::TJson2ProtoConfig::EnumSnakeCaseInsensitive)
+ );
+} // TestComplexMapAsObject_EnumStringCaseInsensetive
+
+Y_UNIT_TEST(TestComplexMapAsObject_Float) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableFloat();
+ items["key1"] = 0.1f;
+ items["key2"] = 0.2f;
+ items["key3"] = 0.3f;
+ },
+ R"_({"Float":{"key1":0.1,"key2":0.2,"key3":0.3}})_");
+} // TestComplexMapAsObject_Float
+
+Y_UNIT_TEST(TestComplexMapAsObject_Double) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ auto& items = *proto.MutableDouble();
+ items["key1"] = 0.1L;
+ items["key2"] = 0.2L;
+ items["key3"] = 0.3L;
+ },
+ R"_({"Double":{"key1":0.1,"key2":0.2,"key3":0.3}})_");
+} // TestComplexMapAsObject_Double
+
+Y_UNIT_TEST(TestComplexMapAsObject_Nested) {
+ TestComplexMapAsObject(
+ [](TComplexMapType& proto) {
+ TComplexMapType inner;
+ auto& innerItems = *inner.MutableString();
+ innerItems["key"] = "value";
+ auto& items = *proto.MutableNested();
+ items["key1"] = inner;
+ items["key2"] = inner;
+ items["key3"] = inner;
+ },
+ R"_({"Nested":{"key1":{"String":{"key":"value"}},"key2":{"String":{"key":"value"}},"key3":{"String":{"key":"value"}}}})_");
+} // TestComplexMapAsObject_Nested
+
+Y_UNIT_TEST(TestMapAsObjectConfigNotSet) {
+ TString modelStr(R"_({"Items":{"key":"value"}})_");
+
+ TJson2ProtoConfig config;
+ UNIT_ASSERT_EXCEPTION_CONTAINS(
+ Json2Proto<TMapType>(modelStr, config), yexception,
+ "Map as object representation is not allowed");
+} // TestMapAsObjectNotSet
+
+Y_UNIT_TEST(TestMergeFlatOptional) {
+ const NJson::TJsonValue& json = CreateFlatJson();
+
+ NJson::TJsonValue patch;
+ patch["I32"] = 5;
+ patch["Bool"] = false;
+ patch["String"] = "abacaba";
+ patch["Double"] = 0.123;
+
+ TFlatOptional proto;
+ UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto));
+ UNIT_ASSERT_NO_EXCEPTION(MergeJson2Proto(patch, proto));
+
+ TFlatRequired modelProto;
+ FillFlatProto(&modelProto);
+ modelProto.SetI32(5);
+ modelProto.SetBool(false);
+ modelProto.SetString("abacaba");
+ modelProto.SetDouble(0.123);
+
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+} // TestMergeFlatOptional
+
+Y_UNIT_TEST(TestMergeFlatRequired) {
+ const NJson::TJsonValue& json = CreateFlatJson();
+
+ NJson::TJsonValue patch;
+ patch["I32"] = 5;
+ patch["Bool"] = false;
+ patch["String"] = "abacaba";
+ patch["Double"] = 0.123;
+
+ TFlatRequired proto;
+ UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto));
+ UNIT_ASSERT_NO_EXCEPTION(MergeJson2Proto(patch, proto));
+
+ TFlatRequired modelProto;
+ FillFlatProto(&modelProto);
+ modelProto.SetI32(5);
+ modelProto.SetBool(false);
+ modelProto.SetString("abacaba");
+ modelProto.SetDouble(0.123);
+
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+} // TestMergeFlatRequired
+
+Y_UNIT_TEST(TestMergeComposite) {
+ const NJson::TJsonValue& json = CreateCompositeJson();
+
+ NJson::TJsonValue patch;
+ patch["Part"]["I32"] = 5;
+ patch["Part"]["Bool"] = false;
+ patch["Part"]["String"] = "abacaba";
+ patch["Part"]["Double"] = 0.123;
+
+ TCompositeOptional proto;
+ UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto));
+ UNIT_ASSERT_NO_EXCEPTION(MergeJson2Proto(patch, proto));
+
+ TCompositeOptional modelProto;
+ FillCompositeProto(&modelProto);
+ modelProto.MutablePart()->SetI32(5);
+ modelProto.MutablePart()->SetBool(false);
+ modelProto.MutablePart()->SetString("abacaba");
+ modelProto.MutablePart()->SetDouble(0.123);
+
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+} // TestMergeComposite
+
+Y_UNIT_TEST(TestMergeRepeatedReplace) {
+ const NJson::TJsonValue& json = CreateRepeatedFlatJson();
+
+ NJson::TJsonValue patch;
+ patch["I32"].AppendValue(5);
+ patch["I32"].AppendValue(6);
+ patch["String"].AppendValue("abacaba");
+
+ TFlatRepeated proto;
+ TJson2ProtoConfig config;
+ config.ReplaceRepeatedFields = true;
+ UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto));
+ UNIT_ASSERT_NO_EXCEPTION(MergeJson2Proto(patch, proto, config));
+
+ TFlatRepeated modelProto;
+ FillRepeatedProto(&modelProto);
+ modelProto.ClearI32();
+ modelProto.AddI32(5);
+ modelProto.AddI32(6);
+ modelProto.ClearString();
+ modelProto.AddString("abacaba");
+
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+} // TestMergeRepeatedReplace
+
+Y_UNIT_TEST(TestMergeRepeatedAppend) {
+ const NJson::TJsonValue& json = CreateRepeatedFlatJson();
+
+ NJson::TJsonValue patch;
+ patch["I32"].AppendValue(5);
+ patch["I32"].AppendValue(6);
+ patch["String"].AppendValue("abacaba");
+
+ TFlatRepeated proto;
+ UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto));
+ UNIT_ASSERT_NO_EXCEPTION(MergeJson2Proto(patch, proto));
+
+ TFlatRepeated modelProto;
+ FillRepeatedProto(&modelProto);
+ modelProto.AddI32(5);
+ modelProto.AddI32(6);
+ modelProto.AddString("abacaba");
+
+ UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto);
+} // TestMergeRepeatedAppend
+
+Y_UNIT_TEST(TestEmptyStringForCastFromString) {
+ NJson::TJsonValue json;
+ json["I32"] = "";
+ json["Bool"] = "";
+ json["OneString"] = "";
+
+ TJson2ProtoConfig config;
+ config.SetCastFromString(true);
+ config.SetDoNotCastEmptyStrings(true);
+ TFlatOptional proto;
+ UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto, config));
+ UNIT_ASSERT(!proto.HasBool());
+ UNIT_ASSERT(!proto.HasI32());
+ UNIT_ASSERT(proto.HasOneString());
+ UNIT_ASSERT_EQUAL("", proto.GetOneString());
+} // TestEmptyStringForCastFromString
+
+Y_UNIT_TEST(TestAllowComments) {
+ constexpr TStringBuf json = R"(
+{
+ "I32": 4, // comment1
+/*
+ comment2
+ {}
+ qwer
+*/
+ "I64": 3423
+}
+
+)";
+
+ TJson2ProtoConfig config;
+ TFlatOptional proto;
+ UNIT_ASSERT_EXCEPTION_CONTAINS(Json2Proto(json, proto, config), yexception, "Error: Missing a name for object member");
+
+ config.SetAllowComments(true);
+ UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto, config));
+ UNIT_ASSERT_VALUES_EQUAL(proto.GetI32(), 4);
+ UNIT_ASSERT_VALUES_EQUAL(proto.GetI64(), 3423);
+} // TestAllowComments
+
+} // TJson2ProtoTest
diff --git a/library/cpp/protobuf/json/ut/proto.h b/library/cpp/protobuf/json/ut/proto.h
new file mode 100644
index 0000000000..8183bfc8e1
--- /dev/null
+++ b/library/cpp/protobuf/json/ut/proto.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#include <util/generic/hash_set.h>
+#include <util/generic/string.h>
+
+#include <util/system/defaults.h>
+
+namespace NProtobufJsonTest {
+ template <typename TProto>
+ inline void
+ FillFlatProto(TProto* proto,
+ const THashSet<TString>& skippedFields = THashSet<TString>()) {
+#define DEFINE_FIELD(name, value) \
+ if (skippedFields.find(#name) == skippedFields.end()) \
+ proto->Set##name(value);
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+ }
+
+ template <typename TRepeatedField, typename TValue>
+ inline void
+ AddValue(TRepeatedField* field, TValue value) {
+ field->Add(value);
+ }
+
+ inline void
+ AddValue(google::protobuf::RepeatedPtrField<TString>* field, const TString& value) {
+ *(field->Add()) = value;
+ }
+
+ inline void
+ FillRepeatedProto(TFlatRepeated* proto,
+ const THashSet<TString>& skippedFields = THashSet<TString>()) {
+#define DEFINE_REPEATED_FIELD(name, type, ...) \
+ if (skippedFields.find(#name) == skippedFields.end()) { \
+ type values[] = {__VA_ARGS__}; \
+ for (size_t i = 0, end = Y_ARRAY_SIZE(values); i < end; ++i) { \
+ AddValue(proto->Mutable##name(), values[i]); \
+ } \
+ }
+#include <library/cpp/protobuf/json/ut/repeated_fields.incl>
+#undef DEFINE_REPEATED_FIELD
+ }
+
+ template <typename TProto>
+ inline void
+ FillCompositeProto(TProto* proto, const THashSet<TString>& skippedFields = THashSet<TString>()) {
+ FillFlatProto(proto->MutablePart(), skippedFields);
+ }
+
+#define UNIT_ASSERT_PROTOS_EQUAL(lhs, rhs) \
+ do { \
+ if (lhs.SerializeAsString() != rhs.SerializeAsString()) { \
+ Cerr << ">>>>>>>>>> lhs != rhs:" << Endl; \
+ Cerr << lhs.DebugString() << Endl; \
+ Cerr << rhs.DebugString() << Endl; \
+ UNIT_ASSERT_STRINGS_EQUAL(lhs.DebugString(), rhs.DebugString()); \
+ UNIT_ASSERT_STRINGS_EQUAL(lhs.SerializeAsString(), rhs.SerializeAsString()); \
+ } \
+ } while (false);
+
+}
diff --git a/library/cpp/protobuf/json/ut/proto2json_ut.cpp b/library/cpp/protobuf/json/ut/proto2json_ut.cpp
new file mode 100644
index 0000000000..07e52d7f2f
--- /dev/null
+++ b/library/cpp/protobuf/json/ut/proto2json_ut.cpp
@@ -0,0 +1,1022 @@
+#include "json.h"
+#include "proto.h"
+
+#include <library/cpp/protobuf/json/ut/test.pb.h>
+
+#include <library/cpp/json/json_value.h>
+#include <library/cpp/json/json_reader.h>
+#include <library/cpp/json/json_writer.h>
+
+#include <library/cpp/protobuf/json/proto2json.h>
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/generic/hash_set.h>
+#include <util/generic/string.h>
+#include <util/generic/ylimits.h>
+
+#include <util/stream/str.h>
+
+#include <util/system/defaults.h>
+#include <util/system/yassert.h>
+
+#include <limits>
+
+using namespace NProtobufJson;
+using namespace NProtobufJsonTest;
+
+Y_UNIT_TEST_SUITE(TProto2JsonFlatTest) {
+ Y_UNIT_TEST(TestFlatDefault) {
+ using namespace ::google::protobuf;
+ TFlatDefault proto;
+ NJson::TJsonValue json;
+ TProto2JsonConfig cfg;
+ cfg.SetMissingSingleKeyMode(TProto2JsonConfig::MissingKeyDefault);
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, cfg));
+#define DEFINE_FIELD(name, value) \
+ { \
+ auto descr = proto.GetMetadata().descriptor->FindFieldByName(#name); \
+ UNIT_ASSERT(descr); \
+ UNIT_ASSERT(json.Has(#name)); \
+ switch (descr->cpp_type()) { \
+ case FieldDescriptor::CPPTYPE_INT32: \
+ UNIT_ASSERT(descr->default_value_int32() == json[#name].GetIntegerRobust()); \
+ break; \
+ case FieldDescriptor::CPPTYPE_INT64: \
+ UNIT_ASSERT(descr->default_value_int64() == json[#name].GetIntegerRobust()); \
+ break; \
+ case FieldDescriptor::CPPTYPE_UINT32: \
+ UNIT_ASSERT(descr->default_value_uint32() == json[#name].GetUIntegerRobust()); \
+ break; \
+ case FieldDescriptor::CPPTYPE_UINT64: \
+ UNIT_ASSERT(descr->default_value_uint32() == json[#name].GetUIntegerRobust()); \
+ break; \
+ case FieldDescriptor::CPPTYPE_DOUBLE: \
+ UNIT_ASSERT(descr->default_value_double() == json[#name].GetDoubleRobust()); \
+ break; \
+ case FieldDescriptor::CPPTYPE_FLOAT: \
+ UNIT_ASSERT(descr->default_value_float() == json[#name].GetDoubleRobust()); \
+ break; \
+ case FieldDescriptor::CPPTYPE_BOOL: \
+ UNIT_ASSERT(descr->default_value_bool() == json[#name].GetBooleanRobust()); \
+ break; \
+ case FieldDescriptor::CPPTYPE_ENUM: \
+ UNIT_ASSERT(descr->default_value_enum()->number() == json[#name].GetIntegerRobust()); \
+ break; \
+ case FieldDescriptor::CPPTYPE_STRING: \
+ UNIT_ASSERT(descr->default_value_string() == json[#name].GetStringRobust()); \
+ break; \
+ default: \
+ break; \
+ } \
+ }
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+ }
+
+ Y_UNIT_TEST(TestNameGenerator) {
+ TNameGeneratorType proto;
+ proto.SetField(42);
+
+ TProto2JsonConfig cfg;
+ cfg.SetNameGenerator([](const NProtoBuf::FieldDescriptor&) { return "42"; });
+
+ TStringStream str;
+ Proto2Json(proto, str, cfg);
+
+ UNIT_ASSERT_STRINGS_EQUAL(R"({"42":42})", str.Str());
+ }
+
+ Y_UNIT_TEST(TestEnumValueGenerator) {
+ TEnumValueGeneratorType proto;
+ proto.SetEnum(TEnumValueGeneratorType::ENUM_42);
+
+ TProto2JsonConfig cfg;
+ cfg.SetEnumValueGenerator([](const NProtoBuf::EnumValueDescriptor&) { return "42"; });
+
+ TStringStream str;
+ Proto2Json(proto, str, cfg);
+
+ UNIT_ASSERT_STRINGS_EQUAL(R"({"Enum":"42"})", str.Str());
+ }
+
+ Y_UNIT_TEST(TestFlatOptional){
+ {TFlatOptional proto;
+ FillFlatProto(&proto);
+ const NJson::TJsonValue& modelJson = CreateFlatJson();
+ {
+ NJson::TJsonValue json;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+
+ {
+ TStringStream jsonStream;
+ NJson::TJsonValue json;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));
+ UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ } // streamed
+}
+
+ // Try to skip each field
+#define DEFINE_FIELD(name, value) \
+ { \
+ THashSet<TString> skippedField; \
+ skippedField.insert(#name); \
+ TFlatOptional proto; \
+ FillFlatProto(&proto, skippedField); \
+ const NJson::TJsonValue& modelJson = CreateFlatJson(skippedField); \
+ NJson::TJsonValue json; \
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); \
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \
+ { \
+ TStringStream jsonStream; \
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream)); \
+ UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); \
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \
+ } \
+ }
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+} // TestFlatOptional
+
+Y_UNIT_TEST(TestFlatRequired){
+ {TFlatRequired proto;
+FillFlatProto(&proto);
+const NJson::TJsonValue& modelJson = CreateFlatJson();
+{
+ NJson::TJsonValue json;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+}
+
+{
+ TStringStream jsonStream;
+ NJson::TJsonValue json;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));
+ UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+} // streamed
+}
+
+// Try to skip each field
+#define DEFINE_FIELD(name, value) \
+ { \
+ THashSet<TString> skippedField; \
+ skippedField.insert(#name); \
+ TFlatRequired proto; \
+ FillFlatProto(&proto, skippedField); \
+ const NJson::TJsonValue& modelJson = CreateFlatJson(skippedField); \
+ NJson::TJsonValue json; \
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); \
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \
+ { \
+ TStringStream jsonStream; \
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream)); \
+ UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); \
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \
+ } \
+ }
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+} // TestFlatRequired
+
+Y_UNIT_TEST(TestFlatRepeated) {
+ {
+ TFlatRepeated proto;
+ FillRepeatedProto(&proto);
+ const NJson::TJsonValue& modelJson = CreateRepeatedFlatJson();
+ {
+ NJson::TJsonValue json;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+
+ {
+ TStringStream jsonStream;
+ NJson::TJsonValue json;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));
+ UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ } // streamed
+ }
+
+ TProto2JsonConfig config;
+ config.SetMissingRepeatedKeyMode(TProto2JsonConfig::MissingKeySkip);
+
+ // Try to skip each field
+#define DEFINE_REPEATED_FIELD(name, ...) \
+ { \
+ THashSet<TString> skippedField; \
+ skippedField.insert(#name); \
+ TFlatRepeated proto; \
+ FillRepeatedProto(&proto, skippedField); \
+ const NJson::TJsonValue& modelJson = CreateRepeatedFlatJson(skippedField); \
+ NJson::TJsonValue json; \
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config)); \
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \
+ { \
+ TStringStream jsonStream; \
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream, config)); \
+ UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); \
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \
+ } \
+ }
+#include <library/cpp/protobuf/json/ut/repeated_fields.incl>
+#undef DEFINE_REPEATED_FIELD
+} // TestFlatRepeated
+
+Y_UNIT_TEST(TestCompositeOptional){
+ {TCompositeOptional proto;
+FillCompositeProto(&proto);
+const NJson::TJsonValue& modelJson = CreateCompositeJson();
+{
+ NJson::TJsonValue json;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+}
+
+{
+ TStringStream jsonStream;
+ NJson::TJsonValue json;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));
+ UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+} // streamed
+}
+
+// Try to skip each field
+#define DEFINE_FIELD(name, value) \
+ { \
+ THashSet<TString> skippedField; \
+ skippedField.insert(#name); \
+ TCompositeOptional proto; \
+ FillCompositeProto(&proto, skippedField); \
+ const NJson::TJsonValue& modelJson = CreateCompositeJson(skippedField); \
+ NJson::TJsonValue json; \
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); \
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \
+ { \
+ TStringStream jsonStream; \
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream)); \
+ UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); \
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \
+ } \
+ }
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+} // TestCompositeOptional
+
+Y_UNIT_TEST(TestCompositeRequired){
+ {TCompositeRequired proto;
+FillCompositeProto(&proto);
+const NJson::TJsonValue& modelJson = CreateCompositeJson();
+{
+ NJson::TJsonValue json;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+}
+
+{
+ TStringStream jsonStream;
+ NJson::TJsonValue json;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));
+ UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+} // streamed
+}
+
+// Try to skip each field
+#define DEFINE_FIELD(name, value) \
+ { \
+ THashSet<TString> skippedField; \
+ skippedField.insert(#name); \
+ TCompositeRequired proto; \
+ FillCompositeProto(&proto, skippedField); \
+ const NJson::TJsonValue& modelJson = CreateCompositeJson(skippedField); \
+ NJson::TJsonValue json; \
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); \
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \
+ { \
+ TStringStream jsonStream; \
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream)); \
+ UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); \
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \
+ } \
+ }
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+} // TestCompositeRequired
+
+Y_UNIT_TEST(TestCompositeRepeated) {
+ {
+ TFlatOptional partProto;
+ FillFlatProto(&partProto);
+ TCompositeRepeated proto;
+ proto.AddPart()->CopyFrom(partProto);
+
+ NJson::TJsonValue modelJson;
+ NJson::TJsonValue modelArray;
+ modelArray.AppendValue(CreateFlatJson());
+ modelJson.InsertValue("Part", modelArray);
+ {
+ NJson::TJsonValue json;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+
+ {
+ TStringStream jsonStream;
+ NJson::TJsonValue json;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));
+ UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ } // streamed
+ }
+
+ {
+ // Array of messages with each field skipped
+ TCompositeRepeated proto;
+ NJson::TJsonValue modelArray;
+
+#define DEFINE_REPEATED_FIELD(name, ...) \
+ { \
+ THashSet<TString> skippedField; \
+ skippedField.insert(#name); \
+ TFlatOptional partProto; \
+ FillFlatProto(&partProto, skippedField); \
+ proto.AddPart()->CopyFrom(partProto); \
+ modelArray.AppendValue(CreateFlatJson(skippedField)); \
+ }
+#include <library/cpp/protobuf/json/ut/repeated_fields.incl>
+#undef DEFINE_REPEATED_FIELD
+
+ NJson::TJsonValue modelJson;
+ modelJson.InsertValue("Part", modelArray);
+
+ {
+ NJson::TJsonValue json;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+
+ {
+ TStringStream jsonStream;
+ NJson::TJsonValue json;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));
+ UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ } // streamed
+ }
+} // TestCompositeRepeated
+
+Y_UNIT_TEST(TestEnumConfig) {
+ {
+ TFlatOptional proto;
+ proto.SetEnum(E_1);
+ NJson::TJsonValue modelJson;
+ modelJson.InsertValue("Enum", 1);
+ NJson::TJsonValue json;
+ TProto2JsonConfig config;
+ config.EnumMode = TProto2JsonConfig::EnumNumber;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+
+ {
+ TFlatOptional proto;
+ proto.SetEnum(E_1);
+ NJson::TJsonValue modelJson;
+ modelJson.InsertValue("Enum", "E_1");
+ NJson::TJsonValue json;
+ TProto2JsonConfig config;
+ config.EnumMode = TProto2JsonConfig::EnumName;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+
+ {
+ TFlatOptional proto;
+ proto.SetEnum(E_1);
+ NJson::TJsonValue modelJson;
+ modelJson.InsertValue("Enum", "NProtobufJsonTest.E_1");
+ NJson::TJsonValue json;
+ TProto2JsonConfig config;
+ config.EnumMode = TProto2JsonConfig::EnumFullName;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+
+ {
+ TFlatOptional proto;
+ proto.SetEnum(E_1);
+ NJson::TJsonValue modelJson;
+ modelJson.InsertValue("Enum", "e_1");
+ NJson::TJsonValue json;
+ TProto2JsonConfig config;
+ config.EnumMode = TProto2JsonConfig::EnumNameLowerCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+
+ {
+ TFlatOptional proto;
+ proto.SetEnum(E_1);
+ NJson::TJsonValue modelJson;
+ modelJson.InsertValue("Enum", "nprotobufjsontest.e_1");
+ NJson::TJsonValue json;
+ TProto2JsonConfig config;
+ config.EnumMode = TProto2JsonConfig::EnumFullNameLowerCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+} // TestEnumConfig
+
+Y_UNIT_TEST(TestMissingSingleKeyConfig) {
+ {
+ TFlatOptional proto;
+ NJson::TJsonValue modelJson(NJson::JSON_MAP);
+ NJson::TJsonValue json;
+ TProto2JsonConfig config;
+ config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeySkip;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+
+ {
+ NJson::TJsonValue modelJson;
+#define DEFINE_FIELD(name, value) \
+ modelJson.InsertValue(#name, NJson::TJsonValue(NJson::JSON_NULL));
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+
+ TFlatOptional proto;
+ NJson::TJsonValue json;
+ TProto2JsonConfig config;
+ config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeyNull;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+ {
+ // Test MissingKeyExplicitDefaultThrowRequired for non explicit default values.
+ TFlatOptional proto;
+ NJson::TJsonValue modelJson(NJson::JSON_MAP);
+ NJson::TJsonValue json;
+ TProto2JsonConfig config;
+ config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+ {
+ // Test MissingKeyExplicitDefaultThrowRequired for explicit default values.
+ NJson::TJsonValue modelJson;
+ modelJson["String"] = "value";
+
+ TSingleDefaultString proto;
+ NJson::TJsonValue json;
+ TProto2JsonConfig config;
+ config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+ {
+ // Test MissingKeyExplicitDefaultThrowRequired for empty required values.
+ TFlatRequired proto;
+ NJson::TJsonValue json;
+ TProto2JsonConfig config;
+ config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired;
+ UNIT_ASSERT_EXCEPTION_CONTAINS(Proto2Json(proto, json, config), yexception, "Empty required protobuf field");
+ }
+ {
+ // Test MissingKeyExplicitDefaultThrowRequired for required value.
+ TSingleRequiredString proto;
+ NJson::TJsonValue json;
+ TProto2JsonConfig config;
+ config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired;
+
+ UNIT_ASSERT_EXCEPTION_CONTAINS(Proto2Json(proto, json, config), yexception, "Empty required protobuf field");
+
+ NJson::TJsonValue modelJson;
+ modelJson["String"] = "value";
+ proto.SetString("value");
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+} // TestMissingSingleKeyConfig
+
+Y_UNIT_TEST(TestMissingRepeatedKeyNoConfig) {
+ {
+ TFlatRepeated proto;
+ NJson::TJsonValue modelJson(NJson::JSON_MAP);
+ NJson::TJsonValue json;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+} // TestMissingRepeatedKeyNoConfig
+
+Y_UNIT_TEST(TestMissingRepeatedKeyConfig) {
+ {
+ TFlatRepeated proto;
+ NJson::TJsonValue modelJson(NJson::JSON_MAP);
+ NJson::TJsonValue json;
+ TProto2JsonConfig config;
+ config.MissingRepeatedKeyMode = TProto2JsonConfig::MissingKeySkip;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+
+ {
+ NJson::TJsonValue modelJson;
+#define DEFINE_FIELD(name, value) \
+ modelJson.InsertValue(#name, NJson::TJsonValue(NJson::JSON_NULL));
+#include <library/cpp/protobuf/json/ut/fields.incl>
+#undef DEFINE_FIELD
+
+ TFlatRepeated proto;
+ NJson::TJsonValue json;
+ TProto2JsonConfig config;
+ config.MissingRepeatedKeyMode = TProto2JsonConfig::MissingKeyNull;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+ {
+ TFlatRepeated proto;
+ NJson::TJsonValue modelJson(NJson::JSON_MAP);
+ NJson::TJsonValue json;
+ TProto2JsonConfig config;
+ config.MissingRepeatedKeyMode = TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired;
+
+ // SHould be same as MissingKeySkip
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
+ UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
+ }
+} // TestMissingRepeatedKeyConfig
+
+Y_UNIT_TEST(TestEscaping) {
+ // No escape
+ {
+ TString modelStr(R"_({"String":"value\""})_");
+
+ TFlatOptional proto;
+ proto.SetString(R"_(value")_");
+ TStringStream jsonStr;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+
+ // TEscapeJTransform
+ {
+ TString modelStr(R"_({"String":"value\""})_");
+
+ TFlatOptional proto;
+ proto.SetString(R"_(value")_");
+ TProto2JsonConfig config;
+ config.StringTransforms.push_back(new TEscapeJTransform<false, true>());
+ TStringStream jsonStr;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(modelStr, jsonStr.Str());
+ }
+
+ // TCEscapeTransform
+ {
+ TString modelStr(R"_({"String":"value\""})_");
+
+ TFlatOptional proto;
+ proto.SetString(R"_(value")_");
+ TProto2JsonConfig config;
+ config.StringTransforms.push_back(new TCEscapeTransform());
+ TStringStream jsonStr;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+
+ // TSafeUtf8CEscapeTransform
+ {
+ TString modelStr(R"_({"String":"value\""})_");
+
+ TFlatOptional proto;
+ proto.SetString(R"_(value")_");
+ TProto2JsonConfig config;
+ config.StringTransforms.push_back(new TSafeUtf8CEscapeTransform());
+ TStringStream jsonStr;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+} // TestEscaping
+
+class TBytesTransform: public IStringTransform {
+public:
+ int GetType() const override {
+ return 0;
+ }
+ void Transform(TString&) const override {
+ }
+ void TransformBytes(TString& str) const override {
+ str = "bytes";
+ }
+};
+
+Y_UNIT_TEST(TestBytesTransform) {
+ // Test that string field is not changed
+ {
+ TString modelStr(R"_({"String":"value"})_");
+
+ TFlatOptional proto;
+ proto.SetString(R"_(value)_");
+ TProto2JsonConfig config;
+ config.StringTransforms.push_back(new TBytesTransform());
+ TStringStream jsonStr;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+
+ // Test that bytes field is changed
+ {
+ TString modelStr(R"_({"Bytes":"bytes"})_");
+
+ TFlatOptional proto;
+ proto.SetBytes(R"_(value)_");
+ TProto2JsonConfig config;
+ config.StringTransforms.push_back(new TBytesTransform());
+ TStringStream jsonStr;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+}
+
+Y_UNIT_TEST(TestFieldNameMode) {
+ // Original case 1
+ {
+ TString modelStr(R"_({"String":"value"})_");
+
+ TFlatOptional proto;
+ proto.SetString("value");
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+
+ // Original case 2
+ {
+ TString modelStr(R"_({"String":"value"})_");
+
+ TFlatOptional proto;
+ proto.SetString("value");
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ config.FieldNameMode = TProto2JsonConfig::FieldNameOriginalCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+
+ // Lowercase
+ {
+ TString modelStr(R"_({"string":"value"})_");
+
+ TFlatOptional proto;
+ proto.SetString("value");
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ config.FieldNameMode = TProto2JsonConfig::FieldNameLowerCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+
+ // Uppercase
+ {
+ TString modelStr(R"_({"STRING":"value"})_");
+
+ TFlatOptional proto;
+ proto.SetString("value");
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ config.FieldNameMode = TProto2JsonConfig::FieldNameUpperCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+
+ // Camelcase
+ {
+ TString modelStr(R"_({"string":"value"})_");
+
+ TFlatOptional proto;
+ proto.SetString("value");
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ config.FieldNameMode = TProto2JsonConfig::FieldNameCamelCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+ {
+ TString modelStr(R"_({"oneString":"value"})_");
+
+ TFlatOptional proto;
+ proto.SetOneString("value");
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ config.FieldNameMode = TProto2JsonConfig::FieldNameCamelCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+ {
+ TString modelStr(R"_({"oneTwoString":"value"})_");
+
+ TFlatOptional proto;
+ proto.SetOneTwoString("value");
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ config.FieldNameMode = TProto2JsonConfig::FieldNameCamelCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+
+ // snake_case
+ {
+ TString modelStr(R"_({"string":"value"})_");
+
+ TFlatOptional proto;
+ proto.SetString("value");
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ config.FieldNameMode = TProto2JsonConfig::FieldNameSnakeCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+ {
+ TString modelStr(R"_({"one_string":"value"})_");
+
+ TFlatOptional proto;
+ proto.SetOneString("value");
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ config.FieldNameMode = TProto2JsonConfig::FieldNameSnakeCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+ {
+ TString modelStr(R"_({"one_two_string":"value"})_");
+
+ TFlatOptional proto;
+ proto.SetOneTwoString("value");
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ config.FieldNameMode = TProto2JsonConfig::FieldNameSnakeCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+ {
+ TString modelStr(R"_({"a_b_c":"value","user_i_d":"value"})_");
+
+ TFlatOptional proto;
+ proto.SetABC("value");
+ proto.SetUserID("value");
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ config.FieldNameMode = TProto2JsonConfig::FieldNameSnakeCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+
+ // snake_case_dense
+ {
+ TString modelStr(R"_({"abc":"value","user_id":"value"})_");
+
+ TFlatOptional proto;
+ proto.SetABC("value");
+ proto.SetUserID("value");
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ config.FieldNameMode = TProto2JsonConfig::FieldNameSnakeCaseDense;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+
+ // Original case, repeated
+ {
+ TString modelStr(R"_({"I32":[1,2]})_");
+
+ TFlatRepeated proto;
+ proto.AddI32(1);
+ proto.AddI32(2);
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ config.FieldNameMode = TProto2JsonConfig::FieldNameOriginalCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+
+ // Lower case, repeated
+ {
+ TString modelStr(R"_({"i32":[1,2]})_");
+
+ TFlatRepeated proto;
+ proto.AddI32(1);
+ proto.AddI32(2);
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ config.FieldNameMode = TProto2JsonConfig::FieldNameLowerCase;
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+
+ // UseJsonName
+ {
+ // FIXME(CONTRIB-139): see the comment about UseJsonName in json2proto_ut.cpp:
+ // Def_upper json name should be "DefUpper".
+ TString modelStr(R"_({"My-Upper":1,"my-lower":2,"defUpper":3,"defLower":4})_");
+
+ TWithJsonName proto;
+ proto.Setmy_upper(1);
+ proto.SetMy_lower(2);
+ proto.SetDef_upper(3);
+ proto.Setdef_lower(4);
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ config.SetUseJsonName(true);
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+ UNIT_ASSERT_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+ }
+
+ // FieldNameMode with UseJsonName
+ {
+ TProto2JsonConfig config;
+ config.SetFieldNameMode(TProto2JsonConfig::FieldNameLowerCase);
+ UNIT_ASSERT_EXCEPTION_CONTAINS(
+ config.SetUseJsonName(true), yexception, "mutually exclusive");
+ }
+ {
+ TProto2JsonConfig config;
+ config.SetUseJsonName(true);
+ UNIT_ASSERT_EXCEPTION_CONTAINS(
+ config.SetFieldNameMode(TProto2JsonConfig::FieldNameLowerCase), yexception, "mutually exclusive");
+ }
+
+ /// TODO: test missing keys
+} // TestFieldNameMode
+
+Y_UNIT_TEST(TestNan) {
+ TFlatOptional proto;
+ proto.SetDouble(std::numeric_limits<double>::quiet_NaN());
+
+ UNIT_ASSERT_EXCEPTION(Proto2Json(proto, TProto2JsonConfig()), yexception);
+} // TestNan
+
+Y_UNIT_TEST(TestInf) {
+ TFlatOptional proto;
+ proto.SetFloat(std::numeric_limits<float>::infinity());
+
+ UNIT_ASSERT_EXCEPTION(Proto2Json(proto, TProto2JsonConfig()), yexception);
+} // TestInf
+
+Y_UNIT_TEST(TestMap) {
+ TMapType proto;
+
+ auto& items = *proto.MutableItems();
+ items["key1"] = "value1";
+ items["key2"] = "value2";
+ items["key3"] = "value3";
+
+ TString modelStr(R"_({"Items":[{"key":"key3","value":"value3"},{"key":"key2","value":"value2"},{"key":"key1","value":"value1"}]})_");
+
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+
+ NJson::TJsonValue jsonValue, modelValue;
+ NJson::TJsonValue::TArray jsonItems, modelItems;
+ UNIT_ASSERT(NJson::ReadJsonTree(jsonStr.Str(), &jsonValue));
+ UNIT_ASSERT(NJson::ReadJsonTree(modelStr, &modelValue));
+ UNIT_ASSERT(jsonValue.Has("Items"));
+ jsonValue["Items"].GetArray(&jsonItems);
+ modelValue["Items"].GetArray(&modelItems);
+ auto itemKey = [](const NJson::TJsonValue& v) {
+ return v["key"].GetString();
+ };
+ SortBy(jsonItems, itemKey);
+ SortBy(modelItems, itemKey);
+ UNIT_ASSERT_EQUAL(jsonItems, modelItems);
+} // TestMap
+
+Y_UNIT_TEST(TestMapAsObject) {
+ TMapType proto;
+
+ auto& items = *proto.MutableItems();
+ items["key1"] = "value1";
+ items["key2"] = "value2";
+ items["key3"] = "value3";
+
+ TString modelStr(R"_({"Items":{"key3":"value3","key2":"value2","key1":"value1"}})_");
+
+ TStringStream jsonStr;
+ TProto2JsonConfig config;
+ config.MapAsObject = true;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
+
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+} // TestMapAsObject
+
+Y_UNIT_TEST(TestMapWTF) {
+ TMapType proto;
+
+ auto& items = *proto.MutableItems();
+ items["key1"] = "value1";
+ items["key2"] = "value2";
+ items["key3"] = "value3";
+
+ TString modelStr(R"_({"Items":{"key3":"value3","key2":"value2","key1":"value1"}})_");
+
+ TStringStream jsonStr;
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr));
+
+ UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
+} // TestMapWTF
+
+Y_UNIT_TEST(TestStringifyLongNumbers) {
+#define TEST_SINGLE(flag, value, expectString) \
+ do { \
+ TFlatOptional proto; \
+ proto.SetSI64(value); \
+ \
+ TStringStream jsonStr; \
+ TProto2JsonConfig config; \
+ config.SetStringifyLongNumbers(flag); \
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); \
+ if (expectString) { \
+ UNIT_ASSERT_EQUAL(jsonStr.Str(), "{\"SI64\":\"" #value "\"}"); \
+ } else { \
+ UNIT_ASSERT_EQUAL(jsonStr.Str(), "{\"SI64\":" #value "}"); \
+ } \
+ } while (false)
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, 1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, 1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, 10000000000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, -1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, -1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, -10000000000000000, false);
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, 1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, 1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, 10000000000000000, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, -1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, -1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, -10000000000000000, true);
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, 1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, 1000000000, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, 10000000000000000, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, -1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, -1000000000, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, -10000000000000000, true);
+
+#undef TEST_SINGLE
+} // TestStringifyLongNumbers
+
+Y_UNIT_TEST(TestExtension) {
+ TExtensionField proto;
+ proto.SetExtension(bar, 1);
+
+ Y_ASSERT(proto.HasExtension(bar));
+ UNIT_ASSERT_EQUAL(Proto2Json(proto, TProto2JsonConfig()), "{\"NProtobufJsonTest.bar\":1}");
+
+
+ TProto2JsonConfig cfg;
+ cfg.SetExtensionFieldNameMode(TProto2JsonConfig::ExtFldNameShort);
+ UNIT_ASSERT_EQUAL(Proto2Json(proto, cfg), "{\"bar\":1}");
+} // TestExtension
+
+} // TProto2JsonTest
diff --git a/library/cpp/protobuf/json/ut/repeated_fields.incl b/library/cpp/protobuf/json/ut/repeated_fields.incl
new file mode 100644
index 0000000000..e9548917d8
--- /dev/null
+++ b/library/cpp/protobuf/json/ut/repeated_fields.incl
@@ -0,0 +1,21 @@
+// Intentionally no #pragma once
+
+// (Field name == JSON key, Type, Values...)
+DEFINE_REPEATED_FIELD(I32, i32, Min<i32>(), -1, 0, 1, Max<i32>())
+DEFINE_REPEATED_FIELD(I64, i64, Min<i64>(), -1ll, 0ll, 1ll, Max<i64>())
+DEFINE_REPEATED_FIELD(UI32, ui32, 0ul, 1ul, Max<ui32>())
+DEFINE_REPEATED_FIELD(UI64, ui64, 0ull, 1ull, Max<ui64>())
+DEFINE_REPEATED_FIELD(SI32, i32, Min<i32>(), -1, 0, 1, Max<i32>())
+DEFINE_REPEATED_FIELD(SI64, i64, Min<i64>(), -1ll, 0ll, 1ll, Max<i64>())
+DEFINE_REPEATED_FIELD(FI32, ui32, 0, 1, Max<ui32>())
+DEFINE_REPEATED_FIELD(FI64, ui64, 0ull, 1ull, Max<ui64>())
+DEFINE_REPEATED_FIELD(SFI32, i32, Min<i32>(), -1, 0, 1, Max<i32>())
+DEFINE_REPEATED_FIELD(SFI64, i64, Min<i64>(), -1ll, 0ll, 1ll, Max<i64>())
+DEFINE_REPEATED_FIELD(Bool, bool, false, true)
+DEFINE_REPEATED_FIELD(String, TString, "", "Lorem ipsum", "123123")
+DEFINE_REPEATED_FIELD(Bytes, TString, "", "מחשב", "\x1")
+DEFINE_REPEATED_FIELD(Enum, EEnum, E_1, E_2, E_3)
+DEFINE_REPEATED_FIELD(Float, float, 0.0f, 1.0f, 1.123f)
+DEFINE_REPEATED_FIELD(Double, double, 0.0, 1.0, 1.123456789012)
+DEFINE_REPEATED_FIELD(OneString, TString, "", "Lorem ipsum dolor", "1231231")
+DEFINE_REPEATED_FIELD(OneTwoString, TString, "", "Lorem ipsum dolor sit", "12312312")
diff --git a/library/cpp/protobuf/json/ut/string_transform_ut.cpp b/library/cpp/protobuf/json/ut/string_transform_ut.cpp
new file mode 100644
index 0000000000..a31dabcb0f
--- /dev/null
+++ b/library/cpp/protobuf/json/ut/string_transform_ut.cpp
@@ -0,0 +1,106 @@
+#include "json.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+#include <library/cpp/protobuf/json/proto2json.h>
+
+Y_UNIT_TEST_SUITE(TDoubleEscapeTransform) {
+ Y_UNIT_TEST(TestEmptyString) {
+ const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleEscapeTransform();
+ TString s;
+ s = "";
+ transform.Transform(s);
+ UNIT_ASSERT_EQUAL(s, "");
+ }
+
+ Y_UNIT_TEST(TestAlphabeticString) {
+ const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleEscapeTransform();
+ TString s;
+ s = "abacaba";
+ transform.Transform(s);
+ UNIT_ASSERT_EQUAL(s, "abacaba");
+ }
+
+ Y_UNIT_TEST(TestRussianSymbols) {
+ const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleEscapeTransform();
+ TString s;
+ s = "тест";
+ transform.Transform(s);
+ UNIT_ASSERT_EQUAL(s, "\\\\321\\\\202\\\\320\\\\265\\\\321\\\\201\\\\321\\\\202");
+ }
+
+ Y_UNIT_TEST(TestEscapeSpecialSymbols) {
+ const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleEscapeTransform();
+ TString s;
+ s = "aba\\ca\"ba";
+ transform.Transform(s);
+ Cerr << "###" << s << Endl;
+ UNIT_ASSERT_EQUAL(s, "aba\\\\\\\\ca\\\\\\\"ba");
+ }
+}
+
+Y_UNIT_TEST_SUITE(TDoubleUnescapeTransform) {
+ Y_UNIT_TEST(TestEmptyString) {
+ const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleUnescapeTransform();
+ TString s;
+ s = "";
+ transform.Transform(s);
+ UNIT_ASSERT_EQUAL("", s);
+ }
+
+ Y_UNIT_TEST(TestAlphabeticString) {
+ const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleUnescapeTransform();
+ TString s;
+ s = "abacaba";
+ transform.Transform(s);
+ Cerr << "###" << s << Endl;
+ UNIT_ASSERT_EQUAL("abacaba", s);
+ }
+
+ Y_UNIT_TEST(TestRussianSymbols) {
+ const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleUnescapeTransform();
+ TString s;
+ s = "\\\\321\\\\202\\\\320\\\\265\\\\321\\\\201\\\\321\\\\202";
+ transform.Transform(s);
+ UNIT_ASSERT_EQUAL("тест", s);
+ }
+
+ Y_UNIT_TEST(TestEscapeSpecialSymbols) {
+ const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleUnescapeTransform();
+ TString s;
+ s = "aba\\\\\\\\ca\\\\\\\"ba";
+ transform.Transform(s);
+ UNIT_ASSERT_EQUAL("aba\\ca\"ba", s);
+ }
+
+ Y_UNIT_TEST(TestEscapeSpecialSymbolsDifficultCases) {
+ const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleUnescapeTransform();
+ TString s;
+ s = "\\\\\\\\\\\\\\\\";
+ transform.Transform(s);
+ UNIT_ASSERT_EQUAL("\\\\", s);
+
+ s = "\\\\\\\\\\\\\\\"";
+ transform.Transform(s);
+ UNIT_ASSERT_EQUAL("\\\"", s);
+
+ s = "\\\\\\\"\\\\\\\\";
+ transform.Transform(s);
+ UNIT_ASSERT_EQUAL("\"\\", s);
+
+ s = "\\\\\\\"\\\\\\\"";
+ transform.Transform(s);
+ UNIT_ASSERT_EQUAL("\"\"", s);
+
+ s = "\\\\\\\\\\\\\\\\\\\\\\\\";
+ transform.Transform(s);
+ UNIT_ASSERT_EQUAL("\\\\\\", s);
+
+ s = "\\\\\\\\\\\\\\\\\\\\\\\\abacaba\\\\";
+ transform.Transform(s);
+ UNIT_ASSERT_EQUAL("\\\\\\abacaba", s);
+
+ s = "\\\\\\\\\\\\\\\\\\\\\\\\abacaba\\\"";
+ transform.Transform(s);
+ UNIT_ASSERT_EQUAL("\\\\\\abacaba\"", s);
+ }
+}
diff --git a/library/cpp/protobuf/json/ut/test.proto b/library/cpp/protobuf/json/ut/test.proto
new file mode 100644
index 0000000000..0fa996fd41
--- /dev/null
+++ b/library/cpp/protobuf/json/ut/test.proto
@@ -0,0 +1,203 @@
+package NProtobufJsonTest;
+
+enum EEnum {
+ E_0 = 0;
+ E_1 = 1;
+ E_2 = 2;
+ E_3 = 3;
+};
+
+message TFlatOptional {
+ optional int32 I32 = 1;
+ optional int64 I64 = 2;
+ optional uint32 UI32 = 3;
+ optional uint64 UI64 = 4;
+ optional sint32 SI32 = 5;
+ optional sint64 SI64 = 6;
+ optional fixed32 FI32 = 7;
+ optional fixed64 FI64 = 8;
+ optional sfixed32 SFI32 = 9;
+ optional sfixed64 SFI64 = 10;
+
+ optional bool Bool = 11;
+
+ optional string String = 12;
+ optional bytes Bytes = 13;
+
+ optional EEnum Enum = 14;
+
+ optional float Float = 15;
+ optional double Double = 16;
+
+ optional string OneString = 17;
+ optional string OneTwoString = 18;
+ optional string ABC = 19;
+ optional string UserID = 20;
+};
+
+message TFlatRequired {
+ required int32 I32 = 1;
+ required int64 I64 = 2;
+ required uint32 UI32 = 3;
+ required uint64 UI64 = 4;
+ required sint32 SI32 = 5;
+ required sint64 SI64 = 6;
+ required fixed32 FI32 = 7;
+ required fixed64 FI64 = 8;
+ required sfixed32 SFI32 = 9;
+ required sfixed64 SFI64 = 10;
+
+ required bool Bool = 11;
+
+ required string String = 12;
+ required bytes Bytes = 13;
+
+ required EEnum Enum = 14;
+
+ required float Float = 15;
+ required double Double = 16;
+
+ required string OneString = 17;
+ required string OneTwoString = 18;
+ required string ABC = 19;
+ required string UserID = 20;
+};
+
+message TFlatRepeated {
+ repeated int32 I32 = 1;
+ repeated int64 I64 = 2;
+ repeated uint32 UI32 = 3;
+ repeated uint64 UI64 = 4;
+ repeated sint32 SI32 = 5;
+ repeated sint64 SI64 = 6;
+ repeated fixed32 FI32 = 7;
+ repeated fixed64 FI64 = 8;
+ repeated sfixed32 SFI32 = 9;
+ repeated sfixed64 SFI64 = 10;
+
+ repeated bool Bool = 11;
+
+ repeated string String = 12;
+ repeated bytes Bytes = 13;
+
+ repeated EEnum Enum = 14;
+
+ repeated float Float = 15;
+ repeated double Double = 16;
+
+ repeated string OneString = 17;
+ repeated string OneTwoString = 18;
+ repeated string ABC = 19;
+ repeated string UserID = 20;
+};
+
+message TFlatDefault {
+ optional int32 I32 = 1 [default = 132];
+ optional int64 I64 = 2 [default = 164];
+ optional uint32 UI32 = 3 [default = 232];
+ optional uint64 UI64 = 4 [default = 264];
+ optional sint32 SI32 = 5 [default = 332];
+ optional sint64 SI64 = 6 [default = 364];
+ optional fixed32 FI32 = 7 [default = 432];
+ optional fixed64 FI64 = 8 [default = 464];
+ optional sfixed32 SFI32 = 9 [default = 532];
+ optional sfixed64 SFI64 = 10 [default = 564];
+
+ optional bool Bool = 11 [default = true];
+
+ optional string String = 12 [default = "string"];
+ optional bytes Bytes = 13 [default = "bytes"];
+
+ optional EEnum Enum = 14 [default = E_2];
+
+ optional float Float = 15 [default = 0.123];
+ optional double Double = 16 [default = 0.456];
+
+ optional string OneString = 17 [default = "string"];
+ optional string OneTwoString = 18 [default = "string"];
+ optional string ABC = 19 [default = "abc"];
+ optional string UserID = 20 [default = "some_id"];
+};
+
+message TCompositeOptional {
+ optional TFlatOptional Part = 1;
+};
+
+message TCompositeRequired {
+ required TFlatRequired Part = 1;
+};
+
+message TCompositeRepeated {
+ repeated TFlatOptional Part = 1;
+};
+
+message TMapType {
+ map<string, string> Items = 1;
+};
+
+message TNameGeneratorType {
+ optional int32 Field = 1;
+};
+
+message TEnumValueGeneratorType {
+ enum EEnum {
+ ENUM_42 = 1;
+ };
+
+ optional EEnum Enum = 1;
+};
+
+message TComplexMapType {
+ map<int32, int32> I32 = 1;
+ map<int64, int64> I64 = 2;
+ map<uint32, uint32> UI32 = 3;
+ map<uint64, uint64> UI64 = 4;
+ map<sint32, sint32> SI32 = 5;
+ map<sint64, sint64> SI64 = 6;
+ map<fixed32, fixed32> FI32 = 7;
+ map<fixed64, fixed64> FI64 = 8;
+ map<sfixed32, sfixed32> SFI32 = 9;
+ map<sfixed64, sfixed64> SFI64 = 10;
+
+ map<bool, bool> Bool = 11;
+
+ map<string, string> String = 12;
+
+ map<string, EEnum> Enum = 13;
+
+ map<string, float> Float = 14;
+ map<string, double> Double = 15;
+
+ map<string, TComplexMapType> Nested = 16;
+};
+
+message TWithJsonName {
+ optional int32 my_upper = 1 [json_name = "My-Upper"];
+ optional int32 My_lower = 2 [json_name = "my-lower"];
+ optional int32 Def_upper = 3; // json_name = "DefUpper"
+ optional int32 def_lower = 4; // json_name = "defLower"
+}
+
+message TSingleRequiredString {
+ required string String = 1;
+}
+
+message TSingleDefaultString {
+ optional string String = 1 [default = "value"];
+}
+
+message TSingleRepeatedString {
+ repeated string RepeatedString = 1;
+}
+
+message TSingleRepeatedInt {
+ repeated int32 RepeatedInt = 1;
+}
+
+message TExtensionField {
+ extensions 100 to 199;
+}
+
+extend TExtensionField {
+ optional int32 bar = 123;
+} \ No newline at end of file
diff --git a/library/cpp/protobuf/json/ut/util_ut.cpp b/library/cpp/protobuf/json/ut/util_ut.cpp
new file mode 100644
index 0000000000..05101dca28
--- /dev/null
+++ b/library/cpp/protobuf/json/ut/util_ut.cpp
@@ -0,0 +1,42 @@
+#include <library/cpp/protobuf/json/util.h>
+
+#include <library/cpp/testing/unittest/registar.h>
+
+using namespace NProtobufJson;
+
+Y_UNIT_TEST_SUITE(TEqualsTest) {
+ Y_UNIT_TEST(TestEmpty) {
+ UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("", ""));
+ UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("", "_"));
+ UNIT_ASSERT(!EqualsIgnoringCaseAndUnderscores("f", ""));
+ }
+
+ Y_UNIT_TEST(TestTrivial) {
+ UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("f", "f"));
+ UNIT_ASSERT(!EqualsIgnoringCaseAndUnderscores("f", "o"));
+ UNIT_ASSERT(!EqualsIgnoringCaseAndUnderscores("fo", "f"));
+ UNIT_ASSERT(!EqualsIgnoringCaseAndUnderscores("f", "fo"));
+ UNIT_ASSERT(!EqualsIgnoringCaseAndUnderscores("bar", "baz"));
+ }
+
+ Y_UNIT_TEST(TestUnderscores) {
+ UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("foo_bar", "foobar"));
+ UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("foo_bar_", "foobar"));
+ UNIT_ASSERT(!EqualsIgnoringCaseAndUnderscores("foo_bar_z", "foobar"));
+ UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("foo__bar__", "foobar"));
+ UNIT_ASSERT(!EqualsIgnoringCaseAndUnderscores("foo__bar__z", "foobar"));
+ UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("_foo_bar", "foobar"));
+ UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("_foo_bar_", "foobar"));
+ UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("_foo_bar_", "foo___bar"));
+ }
+
+ Y_UNIT_TEST(TestCase) {
+ UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("foo_bar", "FOO_BAR"));
+ UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("foobar", "fooBar"));
+ }
+
+ Y_UNIT_TEST(TestCaseAndUnderscores) {
+ UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("fooBar", "FOO_BAR"));
+ UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("FOO_BAR_BAZ", "fooBar_BAZ"));
+ }
+}
diff --git a/library/cpp/protobuf/json/ut/ya.make b/library/cpp/protobuf/json/ut/ya.make
new file mode 100644
index 0000000000..b60a6d3c17
--- /dev/null
+++ b/library/cpp/protobuf/json/ut/ya.make
@@ -0,0 +1,23 @@
+UNITTEST_FOR(library/cpp/protobuf/json)
+
+OWNER(avitella)
+
+SRCS(
+ filter_ut.cpp
+ json2proto_ut.cpp
+ proto2json_ut.cpp
+ inline_ut.proto
+ inline_ut.cpp
+ string_transform_ut.cpp
+ filter_ut.proto
+ test.proto
+ util_ut.cpp
+)
+
+GENERATE_ENUM_SERIALIZATION(test.pb.h)
+
+PEERDIR(
+ library/cpp/protobuf/json
+)
+
+END()