#include <catch2/catch.hpp> #include <google/protobuf/util/json_util.h> #include <testpb/messages.pb.h> TEST_CASE("simple struct", "[protobuf]") { auto json = R"( { "int_value": 9223372036854775807, "string_value": "foo bar", "int_arr": [4, 2], "string_arr": ["foo", "bar", "baz"], "int_map": { "42": 4, "72": 2, }, "string_map": { "foo": "foo_value", "bar": "bar_value" }, "double_value": 42 } )"; testproto::SimpleStruct msg; google::protobuf::util::JsonParseOptions options; auto status = google::protobuf::util::JsonStringToMessage(json, &msg, options); CHECK(status.error_message() == ""); REQUIRE(status.ok()); CHECK(msg.string_value() == "foo bar"); CHECK(msg.int_value() == 9223372036854775807); CHECK(msg.int_arr().size() == 2); CHECK(msg.int_arr().Get(0) == 4); CHECK(msg.int_arr().Get(1) == 2); CHECK(msg.string_arr().size() == 3); std::vector<std::string> expected{"foo", "bar", "baz"}; CHECK( std::equal( msg.string_arr().begin(), msg.string_arr().end(), expected.begin(), expected.end() ) ); CHECK(msg.int_map_size() == 2); CHECK(msg.int_map().at(42) == 4); CHECK(msg.int_map().at(72) == 2); CHECK(msg.string_map_size() == 2); CHECK(msg.string_map().at("foo") == "foo_value"); CHECK(msg.string_map().at("bar") == "bar_value"); CHECK(msg.double_value() == 42); } TEST_CASE("unmarshal bytes", "[protobuf]") { auto json = R"( { "bytes_value": "AEJmb29iYXI=", } )"; testproto::BytesType msg; google::protobuf::util::JsonParseOptions options; auto status = google::protobuf::util::JsonStringToMessage(json, &msg, options); CHECK(status.error_message() == ""); REQUIRE(status.ok()); using namespace std::string_literals; CHECK(msg.bytes_value() == "\x00\x42"s "foobar"); } TEST_CASE("two dimensional array", "[protobuf]") { auto json = R"( { arr: [ { str_arr: ["foo", "bar"] } ] } )"; testproto::TwoDimensionalArray msg; google::protobuf::util::JsonParseOptions options; auto status = google::protobuf::util::JsonStringToMessage(json, &msg, options); CHECK(status.error_message() == ""); REQUIRE(status.ok()); REQUIRE(msg.arr().size() == 1); REQUIRE(msg.arr().at(0).str_arr().size() == 2); REQUIRE(msg.arr().at(0).str_arr().at(0) == "foo"); REQUIRE(msg.arr().at(0).str_arr().at(1) == "bar"); } TEST_CASE("invalid json", "[protobuf]") { auto json = R"( { "string_value": invalid, } )"; testproto::SimpleStruct msg; google::protobuf::util::JsonParseOptions options; auto status = JsonStringToMessage(json, &msg, options); REQUIRE_FALSE(status.ok()); } TEST_CASE("null/missing integer value", "[protobuf]") { auto json = R"( { "int_value": null, "string_value": "foo bar", } )"; google::protobuf::util::JsonParseOptions options; testproto::SimpleStruct msg; auto status = google::protobuf::util::JsonStringToMessage(json, &msg, options); REQUIRE(status.ok()); REQUIRE(msg.string_value() == "foo bar"); REQUIRE(msg.int_value() == 0); json = R"( { "string_value": "foo bar", } )"; msg = testproto::SimpleStruct{}; status = google::protobuf::util::JsonStringToMessage(json, &msg, options); REQUIRE(status.ok()); REQUIRE(msg.string_value() == "foo bar"); REQUIRE(msg.int_value() == 0); } TEST_CASE("json marshal double", "[protobuf]") { testproto::SimpleStruct msg; msg.set_double_value(42); std::string json; google::protobuf::util::JsonOptions options; options.add_whitespace = true; options.preserve_proto_field_names = true; options.always_print_enums_as_ints = true; auto status = google::protobuf::util::MessageToJsonString(msg, &json, options); auto expected = R"({ "double_value": 42 } )"; REQUIRE(json == expected); } TEST_CASE("json marshal uint64", "[protobuf]") { testproto::SimpleStruct msg; msg.set_uint64_value(42); std::string json; google::protobuf::util::JsonOptions options; options.add_whitespace = true; options.preserve_proto_field_names = true; options.always_print_enums_as_ints = true; auto status = google::protobuf::util::MessageToJsonString(msg, &json, options); auto expected = R"({ "uint64_value": "42" } )"; REQUIRE(json == expected); } TEST_CASE("json marshal naming", "[protobuf]") { testproto::JsonNaming msg; msg.set_foo_value(1); msg.set_barvalue(2); msg.set_bazvalue(3); msg.set_qux_value(4); std::string json; google::protobuf::util::JsonOptions options; options.add_whitespace = true; options.preserve_proto_field_names = true; options.always_print_primitive_fields = true; options.always_print_enums_as_ints = true; auto status = google::protobuf::util::MessageToJsonString(msg, &json, options); auto expected = R"({ "foo_value": 1, "barValue": 2, "BazValue": 3, "quX_Value": 4 } )"; REQUIRE(json == expected); } TEST_CASE("json unmarshal naming", "[protobuf]") { SECTION("accept snake case") { auto json = R"( { "quX_Value": 42, } )"; testproto::JsonNaming msg; google::protobuf::util::JsonParseOptions options; auto status = google::protobuf::util::JsonStringToMessage(json, &msg, options); CHECK(status.error_message() == ""); REQUIRE(status.ok()); CHECK(msg.qux_value() == 42); } SECTION("accept camelCase case") { auto json = R"( { "barValue": 42, } )"; testproto::JsonNaming msg; google::protobuf::util::JsonParseOptions options; auto status = google::protobuf::util::JsonStringToMessage(json, &msg, options); CHECK(status.error_message() == ""); REQUIRE(status.ok()); CHECK(msg.barvalue() == 42); } SECTION("accept CamelCase case") { auto json = R"( { "BazValue": 42, } )"; testproto::JsonNaming msg; google::protobuf::util::JsonParseOptions options; auto status = google::protobuf::util::JsonStringToMessage(json, &msg, options); CHECK(status.error_message() == ""); REQUIRE(status.ok()); CHECK(msg.bazvalue() == 42); } } TEST_CASE("oneof unmarshal", "[protobuf]") { SECTION("accept string value") { auto json = R"( { "string_value": "foo bar", } )"; testproto::OneofValue msg; google::protobuf::util::JsonParseOptions options; auto status = google::protobuf::util::JsonStringToMessage(json, &msg, options); CHECK(status.error_message() == ""); REQUIRE(status.ok()); CHECK(msg.data_case() == testproto::OneofValue::kStringValue); CHECK_FALSE(msg.has_struct_value()); CHECK(msg.string_value() == "foo bar"); } SECTION("accept struct value") { auto json = R"( { "struct_value": { "int_value": 42 } } )"; testproto::OneofValue msg; google::protobuf::util::JsonParseOptions options; auto status = google::protobuf::util::JsonStringToMessage(json, &msg, options); CHECK(status.error_message() == ""); REQUIRE(status.ok()); CHECK(msg.data_case() == testproto::OneofValue::kStructValue); CHECK(msg.has_struct_value()); CHECK(msg.struct_value().int_value() == 42); } } TEST_CASE("oneof marshal", "[protobuf]") { SECTION("marshal string value") { testproto::OneofValue msg; msg.set_string_value("foo bar"); google::protobuf::util::JsonPrintOptions options; options.preserve_proto_field_names = true; std::string json; auto status = google::protobuf::util::MessageToJsonString(msg, &json, options); auto expected = R"({"string_value":"foo bar"})"; CHECK(status.error_message() == ""); REQUIRE(status.ok()); CHECK(json == expected); } SECTION("marshal struct value") { testproto::OneofValue msg; msg.mutable_struct_value()->set_int_value(42); google::protobuf::util::JsonPrintOptions options; options.preserve_proto_field_names = true; std::string json; auto status = google::protobuf::util::MessageToJsonString(msg, &json, options); auto expected = R"({"struct_value":{"int_value":"42"}})"; CHECK(status.error_message() == ""); REQUIRE(status.ok()); CHECK(json == expected); } } TEST_CASE("empty messages with OmitEmpty", "[protobuf]") { testproto::MessageType msg; msg.add_foo_arr(); google::protobuf::util::JsonPrintOptions options; options.preserve_proto_field_names = true; options.always_print_primitive_fields = false; std::string json; auto status = google::protobuf::util::MessageToJsonString(msg, &json, options); auto expected = R"({"foo_arr":[{}]})"; CHECK(json == expected); }