Skip to content

Commit e995f60

Browse files
author
ivanlysenko
committed
feat userver: add Serialize, Parse, and operator<< functions for proto structs
Adds - `formats::json::Value Serialize(const TStruct& obj, formats::serialize::To<formats::json::Value>);` - `TStruct Parse(const formats::json::Value& json, formats::parse::To<TStruct>);` - `logging::LogHelper& operator<<(logging::LogHelper& h, const TStruct& obj);` for easier JSON serialization, deserialization, and logging. commit_hash:260aea1257b8d8035f17331c91a3af976647bfa3
1 parent ba0d6fa commit e995f60

5 files changed

Lines changed: 140 additions & 79 deletions

File tree

.mapping.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3061,6 +3061,7 @@
30613061
"libraries/proto-structs/src/proto-structs/io/userver/proto_structs/duration_conv.cpp":"taxi/uservices/userver/libraries/proto-structs/src/proto-structs/io/userver/proto_structs/duration_conv.cpp",
30623062
"libraries/proto-structs/src/proto-structs/io/userver/proto_structs/time_of_day_conv.cpp":"taxi/uservices/userver/libraries/proto-structs/src/proto-structs/io/userver/proto_structs/time_of_day_conv.cpp",
30633063
"libraries/proto-structs/src/proto-structs/io/userver/proto_structs/timestamp_conv.cpp":"taxi/uservices/userver/libraries/proto-structs/src/proto-structs/io/userver/proto_structs/timestamp_conv.cpp",
3064+
"libraries/proto-structs/src/proto-structs/json.cpp":"taxi/uservices/userver/libraries/proto-structs/src/proto-structs/json.cpp",
30643065
"libraries/proto-structs/src/proto-structs/time_of_day.cpp":"taxi/uservices/userver/libraries/proto-structs/src/proto-structs/time_of_day.cpp",
30653066
"libraries/proto-structs/src/proto-structs/timestamp.cpp":"taxi/uservices/userver/libraries/proto-structs/src/proto-structs/timestamp.cpp",
30663067
"libraries/proto-structs/tests/any_test.cpp":"taxi/uservices/userver/libraries/proto-structs/tests/any_test.cpp",

libraries/proto-structs/include/userver/proto-structs/json.hpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
/// @brief Utilities for converting protobuf structs to/from @ref formats::json::Value
55

66
#include <userver/formats/json/serialize.hpp>
7+
#include <userver/logging/log_helper.hpp>
78
#include <userver/proto-structs/convert.hpp>
89
#include <userver/proto-structs/type_mapping.hpp>
910
#include <userver/protobuf/json/convert.hpp>
@@ -77,4 +78,54 @@ requires traits::ProtoStruct<std::remove_cvref_t<TStruct>>
7778

7879
} // namespace proto_structs
7980

81+
namespace formats::serialize {
82+
83+
/// @brief Converts protobuf struct @a obj to @ref formats::json::Value.
84+
/// Conversion is performed by applying @ref proto_structs::StructToJson to @a obj
85+
/// with default @ref protobuf::json::PrintOptions.
86+
template <typename TStruct>
87+
requires proto_structs::traits::ProtoStruct<std::remove_cvref_t<TStruct>>
88+
json::Value Serialize(const TStruct& obj, To<json::Value>) {
89+
return proto_structs::StructToJson(obj, {});
90+
}
91+
92+
} // namespace formats::serialize
93+
94+
namespace formats::parse {
95+
96+
/// @brief Converts @a json to protobuf struct of type `TStruct`.
97+
/// Conversion is performed by applying @ref proto_structs::JsonToStruct to @a json
98+
/// with default @ref protobuf::json::ParseOptions.
99+
template <typename TStruct>
100+
requires proto_structs::traits::ProtoStruct<std::remove_cvref_t<TStruct>>
101+
TStruct Parse(const json::Value& json, To<TStruct>) {
102+
return proto_structs::JsonToStruct<TStruct>(json, {});
103+
}
104+
105+
} // namespace formats::parse
106+
107+
namespace logging {
108+
109+
namespace impl {
110+
111+
logging::LogHelper& LogMessage(logging::LogHelper& h, const google::protobuf::Message& message);
112+
113+
} // namespace impl
114+
115+
/// @brief Logs the protobuf struct @a obj as a JSON string.
116+
/// The resulting JSON is constructed with always_print_primitive_fields and preserve_proto_field_names set to true,
117+
/// which allows the struct field names and values to be serialized as close as possible to the definition.
118+
template <typename TStruct>
119+
requires proto_structs::traits::ProtoStruct<std::remove_cvref_t<TStruct>>
120+
logging::LogHelper& operator<<(logging::LogHelper& h, const TStruct& obj) {
121+
try {
122+
const auto message = proto_structs::StructToMessage(obj);
123+
return impl::LogMessage(h, message);
124+
} catch (const std::exception& ex) {
125+
return h << "Failed to log struct: " << ex;
126+
}
127+
}
128+
129+
} // namespace logging
130+
80131
USERVER_NAMESPACE_END
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <userver/proto-structs/json.hpp>
2+
3+
#include <google/protobuf/json/json.h>
4+
#include <google/protobuf/message.h>
5+
6+
USERVER_NAMESPACE_BEGIN
7+
8+
namespace logging::impl {
9+
10+
logging::LogHelper& LogMessage(logging::LogHelper& h, const google::protobuf::Message& message) {
11+
auto options = google::protobuf::json::PrintOptions();
12+
options.always_print_primitive_fields = true;
13+
options.preserve_proto_field_names = true;
14+
15+
TProtoStringType out;
16+
const auto status = google::protobuf::json::MessageToJsonString(message, &out, options);
17+
if (status.ok()) {
18+
return h << out;
19+
}
20+
return h << "Failed to log struct: " << status;
21+
}
22+
23+
} // namespace logging::impl
24+
25+
USERVER_NAMESPACE_END

libraries/proto-structs/tests/json_to_struct_test.cpp

Lines changed: 33 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,25 @@ USERVER_NAMESPACE_BEGIN
1010

1111
namespace proto_structs::tests {
1212

13-
TEST(JsonToStruct, JsonValue) {
14-
messages::Scalar expected;
15-
expected.set_f1(true);
16-
expected.set_f2(std::numeric_limits<int32_t>::min());
17-
expected.set_f3(std::numeric_limits<uint32_t>::max());
18-
expected.set_f4(std::numeric_limits<int64_t>::min());
19-
expected.set_f5(std::numeric_limits<uint64_t>::max());
20-
expected.set_f6(static_cast<float>(1.5));
21-
expected.set_f7(-2.5);
22-
expected.set_f8("test1");
23-
expected.set_f9("test2");
24-
expected.set_f10(messages::TestEnum::TEST_ENUM_VALUE1);
25-
expected.set_f11(987);
13+
namespace {
14+
15+
messages::Scalar MakeMessage() {
16+
messages::Scalar msg;
17+
msg.set_f1(true);
18+
msg.set_f2(std::numeric_limits<int32_t>::min());
19+
msg.set_f3(std::numeric_limits<uint32_t>::max());
20+
msg.set_f4(std::numeric_limits<int64_t>::min());
21+
msg.set_f5(std::numeric_limits<uint64_t>::max());
22+
msg.set_f6(static_cast<float>(1.5));
23+
msg.set_f7(-2.5);
24+
msg.set_f8("test1");
25+
msg.set_f9("test2");
26+
msg.set_f10(messages::TestEnum::TEST_ENUM_VALUE1);
27+
msg.set_f11(987);
28+
return msg;
29+
}
2630

31+
formats::json::Value MakeJson() {
2732
auto builder = formats::json::ValueBuilder{formats::common::Type::kObject};
2833
builder["f1"] = true;
2934
builder["f2"] = std::numeric_limits<int32_t>::min();
@@ -37,44 +42,28 @@ TEST(JsonToStruct, JsonValue) {
3742
builder["f9"] = "dGVzdDI="; // base64 encoded "test2"
3843
builder["f10"] = "TEST_ENUM_VALUE1";
3944
builder["f11"] = 987;
45+
return builder.ExtractValue();
46+
}
47+
48+
} // namespace
4049

50+
TEST(JsonToStruct, JsonValue) {
4151
structs::Scalar result;
42-
UASSERT_NO_THROW(result = JsonToStruct<structs::Scalar>(builder.ExtractValue(), {}));
43-
structs::CheckScalarEqual(result, expected);
52+
UASSERT_NO_THROW(result = JsonToStruct<structs::Scalar>(MakeJson(), {}));
53+
structs::CheckScalarEqual(result, MakeMessage());
4454
}
4555

4656
TEST(JsonToStruct, JsonString) {
47-
messages::Scalar expected;
48-
expected.set_f1(true);
49-
expected.set_f2(std::numeric_limits<int32_t>::min());
50-
expected.set_f3(std::numeric_limits<uint32_t>::max());
51-
expected.set_f4(std::numeric_limits<int64_t>::min());
52-
expected.set_f5(std::numeric_limits<uint64_t>::max());
53-
expected.set_f6(static_cast<float>(1.5));
54-
expected.set_f7(-2.5);
55-
expected.set_f8("test1");
56-
expected.set_f9("test2");
57-
expected.set_f10(messages::TestEnum::TEST_ENUM_VALUE1);
58-
expected.set_f11(987);
59-
60-
auto builder = formats::json::ValueBuilder{formats::common::Type::kObject};
61-
builder["f1"] = true;
62-
builder["f2"] = std::numeric_limits<int32_t>::min();
63-
builder["f3"] = std::numeric_limits<uint32_t>::max();
64-
// 64 bits are too large for JSON numbers, so we convert to string
65-
builder["f4"] = std::to_string(std::numeric_limits<int64_t>::min());
66-
builder["f5"] = std::to_string(std::numeric_limits<uint64_t>::max());
67-
builder["f6"] = static_cast<float>(1.5);
68-
builder["f7"] = -2.5;
69-
builder["f8"] = "test1";
70-
builder["f9"] = "dGVzdDI="; // base64 encoded "test2"
71-
builder["f10"] = "TEST_ENUM_VALUE1";
72-
builder["f11"] = 987;
73-
const auto json_string = formats::json::ToString(builder.ExtractValue());
74-
57+
const auto json_string = formats::json::ToString(MakeJson());
7558
structs::Scalar result;
7659
UASSERT_NO_THROW(result = JsonStringToStruct<structs::Scalar>(json_string, {}));
77-
structs::CheckScalarEqual(result, expected);
60+
structs::CheckScalarEqual(result, MakeMessage());
61+
}
62+
63+
TEST(JsonToStruct, Parse) {
64+
structs::Scalar result;
65+
UASSERT_NO_THROW(result = MakeJson().As<structs::Scalar>());
66+
structs::CheckScalarEqual(result, MakeMessage());
7867
}
7968

8069
} // namespace proto_structs::tests

libraries/proto-structs/tests/struct_to_json_test.cpp

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <userver/proto-structs/json.hpp>
44
#include <userver/utest/assert_macros.hpp>
5+
#include <userver/utest/log_capture_fixture.hpp>
56

67
#include "messages.pb.h"
78
#include "structs.hpp"
@@ -10,22 +11,10 @@ USERVER_NAMESPACE_BEGIN
1011

1112
namespace proto_structs::tests {
1213

13-
TEST(StructToJson, JsonValue) {
14-
auto expected_builder = formats::json::ValueBuilder{formats::common::Type::kObject};
15-
expected_builder["f1"] = true;
16-
expected_builder["f2"] = std::numeric_limits<int32_t>::min();
17-
expected_builder["f3"] = std::numeric_limits<uint32_t>::max();
18-
// 64 bits are too large for JSON numbers, so we convert to string
19-
expected_builder["f4"] = std::to_string(std::numeric_limits<int64_t>::min());
20-
expected_builder["f5"] = std::to_string(std::numeric_limits<uint64_t>::max());
21-
expected_builder["f6"] = static_cast<float>(1.5);
22-
expected_builder["f7"] = -2.5;
23-
expected_builder["f8"] = "test1";
24-
expected_builder["f9"] = "dGVzdDI="; // base64 encoded "test2"
25-
expected_builder["f10"] = "TEST_ENUM_VALUE1";
26-
expected_builder["f11"] = 987;
14+
namespace {
2715

28-
structs::Scalar obj{
16+
structs::Scalar MakeStruct() {
17+
return structs::Scalar{
2918
.f1 = true,
3019
.f2 = std::numeric_limits<int32_t>::min(),
3120
.f3 = std::numeric_limits<uint32_t>::max(),
@@ -38,13 +27,9 @@ TEST(StructToJson, JsonValue) {
3827
.f10 = structs::TestEnum::kValue1,
3928
.f11 = 987
4029
};
41-
42-
formats::json::Value json;
43-
UASSERT_NO_THROW(json = StructToJson(obj, {}));
44-
ASSERT_EQ(json, expected_builder.ExtractValue());
4530
}
4631

47-
TEST(StructToJson, JsonString) {
32+
formats::json::Value MakeJson() {
4833
auto expected_builder = formats::json::ValueBuilder{formats::common::Type::kObject};
4934
expected_builder["f1"] = true;
5035
expected_builder["f2"] = std::numeric_limits<int32_t>::min();
@@ -58,24 +43,34 @@ TEST(StructToJson, JsonString) {
5843
expected_builder["f9"] = "dGVzdDI="; // base64 encoded "test2"
5944
expected_builder["f10"] = "TEST_ENUM_VALUE1";
6045
expected_builder["f11"] = 987;
46+
return expected_builder.ExtractValue();
47+
}
6148

62-
structs::Scalar obj{
63-
.f1 = true,
64-
.f2 = std::numeric_limits<int32_t>::min(),
65-
.f3 = std::numeric_limits<uint32_t>::max(),
66-
.f4 = std::numeric_limits<int64_t>::min(),
67-
.f5 = std::numeric_limits<uint64_t>::max(),
68-
.f6 = static_cast<float>(1.5),
69-
.f7 = -2.5,
70-
.f8 = "test1",
71-
.f9 = "test2",
72-
.f10 = structs::TestEnum::kValue1,
73-
.f11 = 987
74-
};
49+
} // namespace
50+
51+
TEST(StructToJson, JsonValue) {
52+
formats::json::Value json;
53+
UASSERT_NO_THROW(json = StructToJson(MakeStruct(), {}));
54+
ASSERT_EQ(json, MakeJson());
55+
}
7556

57+
TEST(StructToJson, JsonString) {
7658
std::string json_string;
77-
UASSERT_NO_THROW(json_string = StructToJsonString(obj, {}));
78-
ASSERT_EQ(formats::json::FromString(json_string), expected_builder.ExtractValue());
59+
UASSERT_NO_THROW(json_string = StructToJsonString(MakeStruct(), {}));
60+
ASSERT_EQ(formats::json::FromString(json_string), MakeJson());
61+
}
62+
63+
TEST(StructToJson, Serialize) {
64+
formats::json::Value json;
65+
UASSERT_NO_THROW(json = formats::json::ValueBuilder{MakeStruct()}.ExtractValue());
66+
ASSERT_EQ(json, MakeJson());
67+
}
68+
69+
using Logging = utest::LogCaptureFixture<>;
70+
71+
TEST_F(Logging, LogStruct) {
72+
const auto obj_string = GetLogCapture().ToStringViaLogging(MakeStruct());
73+
EXPECT_EQ(obj_string, ToString(MakeJson()));
7974
}
8075

8176
} // namespace proto_structs::tests

0 commit comments

Comments
 (0)