// Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "google/protobuf/json/json.h" #include #include #include #include #include #include #include "google/protobuf/duration.pb.h" #include "google/protobuf/field_mask.pb.h" #include "google/protobuf/struct.pb.h" #include "google/protobuf/timestamp.pb.h" #include "google/protobuf/wrappers.pb.h" #include #include #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/string_view.h" #include "google/protobuf/descriptor_database.h" #include "google/protobuf/dynamic_message.h" #include "google/protobuf/io/zero_copy_stream.h" #include "google/protobuf/io/zero_copy_stream_impl_lite.h" #include "google/protobuf/util/json_format.pb.h" #include "google/protobuf/util/json_format_proto3.pb.h" #include "google/protobuf/unittest.pb.h" #include "google/protobuf/util/type_resolver.h" #include "google/protobuf/util/type_resolver_util.h" #include "google/protobuf/stubs/status_macros.h" // Must be included last. #include "google/protobuf/port_def.inc" namespace google { namespace protobuf { namespace json { namespace { using ::google::protobuf::util::TypeResolver; using ::proto3::MapIn; using ::proto3::TestAny; using ::proto3::TestEnumValue; using ::proto3::TestMap; using ::proto3::TestMessage; using ::proto3::TestOneof; using ::proto3::TestWrapper; using ::testing::ElementsAre; using ::testing::IsEmpty; using ::testing::Not; using ::testing::SizeIs; // TODO(b/234474291): Use the gtest versions once that's available in OSS. MATCHER_P(IsOkAndHolds, inner, absl::StrCat("is OK and holds ", testing::PrintToString(inner))) { if (!arg.ok()) { *result_listener << arg.status(); return false; } return testing::ExplainMatchResult(inner, *arg, result_listener); } absl::Status GetStatus(const absl::Status& s) { return s; } template absl::Status GetStatus(const absl::StatusOr& s) { return s.status(); } MATCHER_P(StatusIs, status, absl::StrCat(".status() is ", testing::PrintToString(status))) { return GetStatus(arg).code() == status; } #define EXPECT_OK(x) EXPECT_THAT(x, StatusIs(absl::StatusCode::kOk)) #define ASSERT_OK(x) ASSERT_THAT(x, StatusIs(absl::StatusCode::kOk)) enum class Codec { kReflective, kResolver, }; class JsonTest : public testing::TestWithParam { protected: absl::StatusOr ToJson(const Message& proto, PrintOptions options = {}) { if (GetParam() == Codec::kReflective) { std::string result; RETURN_IF_ERROR(MessageToJsonString(proto, &result, options)); return result; } std::string proto_data = proto.SerializeAsString(); io::ArrayInputStream in(proto_data.data(), proto_data.size()); std::string result; io::StringOutputStream out(&result); RETURN_IF_ERROR(BinaryToJsonStream( resolver_.get(), absl::StrCat("type.googleapis.com/", proto.GetTypeName()), &in, &out, options)); return result; } // The out parameter comes first since `json` tends to be a very long string, // and clang-format does a poor job if it is not the last parameter. absl::Status ToProto(Message& proto, absl::string_view json, ParseOptions options = {}) { if (GetParam() == Codec::kReflective) { return JsonStringToMessage(json, &proto, options); } io::ArrayInputStream in(json.data(), json.size()); std::string result; io::StringOutputStream out(&result); RETURN_IF_ERROR(JsonToBinaryStream( resolver_.get(), absl::StrCat("type.googleapis.com/", proto.GetTypeName()), &in, &out, options)); if (!proto.ParseFromString(result)) { return absl::InternalError("wire format parse failed"); } return absl::OkStatus(); } template absl::StatusOr ToProto(absl::string_view json, ParseOptions options = {}) { Proto proto; RETURN_IF_ERROR(ToProto(proto, json, options)); return proto; } std::unique_ptr resolver_{ google::protobuf::util::NewTypeResolverForDescriptorPool( "type.googleapis.com", DescriptorPool::generated_pool())}; }; INSTANTIATE_TEST_SUITE_P(JsonTestSuite, JsonTest, testing::Values(Codec::kReflective, Codec::kResolver)); TEST_P(JsonTest, TestWhitespaces) { TestMessage m; m.mutable_message_value(); m.set_string_value("foo"); m.add_repeated_bool_value(true); m.add_repeated_bool_value(false); EXPECT_THAT( ToJson(m), IsOkAndHolds( R"({"stringValue":"foo","messageValue":{},"repeatedBoolValue":[true,false]})")); PrintOptions options; options.add_whitespace = true; // Note: whitespace here is significant. EXPECT_THAT(ToJson(m, options), IsOkAndHolds(R"({ "stringValue": "foo", "messageValue": {}, "repeatedBoolValue": [ true, false ] } )")); } TEST_P(JsonTest, TestDefaultValues) { TestMessage m; EXPECT_THAT(ToJson(m), IsOkAndHolds("{}")); PrintOptions options; options.always_print_primitive_fields = true; EXPECT_THAT(ToJson(m, options), IsOkAndHolds("{\"boolValue\":false," "\"int32Value\":0," "\"int64Value\":\"0\"," "\"uint32Value\":0," "\"uint64Value\":\"0\"," "\"floatValue\":0," "\"doubleValue\":0," "\"stringValue\":\"\"," "\"bytesValue\":\"\"," "\"enumValue\":\"FOO\"," "\"repeatedBoolValue\":[]," "\"repeatedInt32Value\":[]," "\"repeatedInt64Value\":[]," "\"repeatedUint32Value\":[]," "\"repeatedUint64Value\":[]," "\"repeatedFloatValue\":[]," "\"repeatedDoubleValue\":[]," "\"repeatedStringValue\":[]," "\"repeatedBytesValue\":[]," "\"repeatedEnumValue\":[]," "\"repeatedMessageValue\":[]" "}")); m.set_string_value("i am a test string value"); m.set_bytes_value("i am a test bytes value"); EXPECT_THAT( ToJson(m, options), IsOkAndHolds("{\"boolValue\":false," "\"int32Value\":0," "\"int64Value\":\"0\"," "\"uint32Value\":0," "\"uint64Value\":\"0\"," "\"floatValue\":0," "\"doubleValue\":0," "\"stringValue\":\"i am a test string value\"," "\"bytesValue\":\"aSBhbSBhIHRlc3QgYnl0ZXMgdmFsdWU=\"," "\"enumValue\":\"FOO\"," "\"repeatedBoolValue\":[]," "\"repeatedInt32Value\":[]," "\"repeatedInt64Value\":[]," "\"repeatedUint32Value\":[]," "\"repeatedUint64Value\":[]," "\"repeatedFloatValue\":[]," "\"repeatedDoubleValue\":[]," "\"repeatedStringValue\":[]," "\"repeatedBytesValue\":[]," "\"repeatedEnumValue\":[]," "\"repeatedMessageValue\":[]" "}")); EXPECT_THAT( ToJson(protobuf_unittest::TestAllTypes(), options), IsOkAndHolds( R"({"optionalInt32":0,"optionalInt64":"0","optionalUint32":0,)" R"("optionalUint64":"0","optionalSint32":0,"optionalSint64":"0","optionalFixed32":0,)" R"("optionalFixed64":"0","optionalSfixed32":0,"optionalSfixed64":"0",)" R"("optionalFloat":0,"optionalDouble":0,"optionalBool":false,"optionalString":"",)" R"("optionalBytes":"","optionalgroup":null,"optionalNestedEnum":"FOO","optionalForeignEnum":"FOREIGN_FOO",)" R"("optionalImportEnum":"IMPORT_FOO","optionalStringPiece":"","optionalCord":"",)" R"("repeatedInt32":[],"repeatedInt64":[],"repeatedUint32":[],"repeatedUint64":[],)" R"("repeatedSint32":[],"repeatedSint64":[],"repeatedFixed32":[],"repeatedFixed64":[],)" R"("repeatedSfixed32":[],"repeatedSfixed64":[],"repeatedFloat":[],"repeatedDouble":[],)" R"("repeatedBool":[],"repeatedString":[],"repeatedBytes":[],"repeatedgroup":[],)" R"("repeatedNestedMessage":[],"repeatedForeignMessage":[],"repeatedImportMessage":[],)" R"("repeatedNestedEnum":[],"repeatedForeignEnum":[],"repeatedImportEnum":[],)" R"("repeatedStringPiece":[],"repeatedCord":[],"repeatedLazyMessage":[],"defaultInt32":41,)" R"("defaultInt64":"42","defaultUint32":43,"defaultUint64":"44","defaultSint32":-45,)" R"("defaultSint64":"46","defaultFixed32":47,"defaultFixed64":"48","defaultSfixed32":49,)" R"("defaultSfixed64":"-50","defaultFloat":51.5,"defaultDouble":52000,"defaultBool":true,)" R"("defaultString":"hello","defaultBytes":"d29ybGQ=","defaultNestedEnum":"BAR",)" R"("defaultForeignEnum":"FOREIGN_BAR","defaultImportEnum":"IMPORT_BAR",)" R"("defaultStringPiece":"abc","defaultCord":"123"})")); EXPECT_THAT( ToJson(protobuf_unittest::TestExtremeDefaultValues(), options), IsOkAndHolds( R"({"escapedBytes":"XDAwMFwwMDFcMDA3XDAxMFwwMTRcblxyXHRcMDEzXFxcJ1wiXDM3Ng==")" R"(,"largeUint32":4294967295,"largeUint64":"18446744073709551615",)" R"("smallInt32":-2147483647,"smallInt64":"-9223372036854775807",)" R"("utf8String":"ሴ","zeroFloat":0,"oneFloat":1,"smallFloat":1.5,)" R"("negativeOneFloat":-1,"negativeFloat":-1.5,"largeFloat":2e+08,)" R"("smallNegativeFloat":-8e-28,"infDouble":0,"negInfDouble":0,)" R"("nanDouble":0,"infFloat":0,"negInfFloat":0,"nanFloat":0,)" R"("cppTrigraph":"? ? ?? ?? ??? ??/ ??-","reallySmallInt32":-2147483648)" R"(,"reallySmallInt64":"-9223372036854775808","stringWithZero":"hel\u0000lo")" R"(,"bytesWithZero":"d29yXDAwMGxk","stringPieceWithZero":"ab\u0000c")" R"(,"cordWithZero":"12\u00003","replacementString":"${unknown}"})")); } TEST_P(JsonTest, TestPreserveProtoFieldNames) { TestMessage m; m.mutable_message_value(); PrintOptions options; options.preserve_proto_field_names = true; EXPECT_THAT(ToJson(m, options), IsOkAndHolds("{\"message_value\":{}}")); } TEST_P(JsonTest, Camels) { protobuf_unittest::TestCamelCaseFieldNames m; m.set_stringfield("sTRINGfIELD"); EXPECT_THAT(ToJson(m), IsOkAndHolds(R"({"StringField":"sTRINGfIELD"})")); } TEST_P(JsonTest, EvilString) { auto m = ToProto(R"json( {"string_value": ")json" "\n\r\b\f\1\2\3" "\"}"); ASSERT_OK(m); EXPECT_EQ(m->string_value(), "\n\r\b\f\1\2\3"); } TEST_P(JsonTest, Unquoted64) { TestMessage m; m.add_repeated_int64_value(0); m.add_repeated_int64_value(42); m.add_repeated_int64_value(-((int64_t{1} << 60) + 1)); m.add_repeated_int64_value(INT64_MAX); // This is a power of two and is therefore representable. m.add_repeated_int64_value(INT64_MIN); m.add_repeated_uint64_value(0); m.add_repeated_uint64_value(42); m.add_repeated_uint64_value((uint64_t{1} << 60) + 1); // This will be UB without the min/max check in RoundTripsThroughDouble(). m.add_repeated_uint64_value(UINT64_MAX); PrintOptions opts; opts.unquote_int64_if_possible = true; EXPECT_THAT( ToJson(m, opts), R"({"repeatedInt64Value":[0,42,"-1152921504606846977","9223372036854775807",-9223372036854775808],)" R"("repeatedUint64Value":[0,42,"1152921504606846977","18446744073709551615"]})"); } TEST_P(JsonTest, TestAlwaysPrintEnumsAsInts) { TestMessage orig; orig.set_enum_value(proto3::BAR); orig.add_repeated_enum_value(proto3::FOO); orig.add_repeated_enum_value(proto3::BAR); PrintOptions print_options; print_options.always_print_enums_as_ints = true; auto printed = ToJson(orig, print_options); ASSERT_THAT(printed, IsOkAndHolds("{\"enumValue\":1,\"repeatedEnumValue\":[0,1]}")); auto parsed = ToProto(*printed); ASSERT_OK(parsed); EXPECT_EQ(parsed->enum_value(), proto3::BAR); EXPECT_THAT(parsed->repeated_enum_value(), ElementsAre(proto3::FOO, proto3::BAR)); } TEST_P(JsonTest, TestPrintEnumsAsIntsWithDefaultValue) { TestEnumValue orig; // orig.set_enum_value1(proto3::FOO) orig.set_enum_value2(proto3::FOO); orig.set_enum_value3(proto3::BAR); PrintOptions print_options; print_options.always_print_enums_as_ints = true; print_options.always_print_primitive_fields = true; auto printed = ToJson(orig, print_options); ASSERT_THAT( printed, IsOkAndHolds("{\"enumValue1\":0,\"enumValue2\":0,\"enumValue3\":1}")); auto parsed = ToProto(*printed); EXPECT_EQ(parsed->enum_value1(), proto3::FOO); EXPECT_EQ(parsed->enum_value2(), proto3::FOO); EXPECT_EQ(parsed->enum_value3(), proto3::BAR); } TEST_P(JsonTest, TestPrintProto2EnumAsIntWithDefaultValue) { protobuf_unittest::TestDefaultEnumValue orig; PrintOptions print_options; print_options.always_print_enums_as_ints = true; print_options.always_print_primitive_fields = true; auto printed = ToJson(orig, print_options); ASSERT_THAT(printed, IsOkAndHolds("{\"enumValue\":2}")); auto parsed = ToProto(*printed); ASSERT_OK(parsed); EXPECT_EQ(parsed->enum_value(), protobuf_unittest::DEFAULT); } TEST_P(JsonTest, QuotedEnumValue) { auto m = ToProto(R"json( {"enumValue1": "1"} )json"); ASSERT_OK(m); EXPECT_THAT(m->enum_value1(), proto3::BAR); } TEST_P(JsonTest, WebSafeBytes) { auto m = ToProto(R"json({ "bytesValue": "-_" })json"); ASSERT_OK(m); EXPECT_EQ(m->bytes_value(), "\xfb"); } TEST_P(JsonTest, ParseMessage) { auto m = ToProto(R"json( { "boolValue": true, "int32Value": 1234567891, "int64Value": -5302428716536692736, "uint32Value": 42, "uint64Value": 530242871653669, "floatValue": 3.4e+38, "doubleValue": -55.3, "stringValue": "foo bar baz", "enumValue": "BAR", "messageValue": { "value": 2048 }, "repeatedBoolValue": [true], "repeatedInt32Value": [0, -42], "repeatedUint64Value": [1, 2], "repeatedDoubleValue": [1.5, -2], "repeatedStringValue": ["foo", "bar ", ""], "repeatedEnumValue": [1, "FOO"], "repeatedMessageValue": [ {"value": 40}, {}, {"value": 96} ] } )json"); ASSERT_OK(m); EXPECT_TRUE(m->bool_value()); EXPECT_EQ(m->int32_value(), 1234567891); EXPECT_EQ(m->int64_value(), -5302428716536692736); EXPECT_EQ(m->uint32_value(), 42); EXPECT_EQ(m->uint64_value(), 530242871653669); EXPECT_EQ(m->float_value(), 3.4e+38f); EXPECT_EQ(m->double_value(), -55.3); // This value is intentionally not a nice // round number in base 2, so its floating point // representation has many digits at the end, which // printing back to JSON must handle well. EXPECT_EQ(m->string_value(), "foo bar baz"); EXPECT_EQ(m->enum_value(), proto3::EnumType::BAR); EXPECT_EQ(m->message_value().value(), 2048); EXPECT_THAT(m->repeated_bool_value(), ElementsAre(true)); EXPECT_THAT(m->repeated_int32_value(), ElementsAre(0, -42)); EXPECT_THAT(m->repeated_uint64_value(), ElementsAre(1, 2)); EXPECT_THAT(m->repeated_double_value(), ElementsAre(1.5, -2)); EXPECT_THAT(m->repeated_string_value(), ElementsAre("foo", "bar ", "")); EXPECT_THAT(m->repeated_enum_value(), ElementsAre(proto3::BAR, proto3::FOO)); ASSERT_THAT(m->repeated_message_value(), SizeIs(3)); EXPECT_EQ(m->repeated_message_value(0).value(), 40); EXPECT_EQ(m->repeated_message_value(1).value(), 0); EXPECT_EQ(m->repeated_message_value(2).value(), 96); EXPECT_THAT( ToJson(*m), IsOkAndHolds( R"({"boolValue":true,"int32Value":1234567891,"int64Value":"-5302428716536692736",)" R"("uint32Value":42,"uint64Value":"530242871653669","floatValue":3.4e+38,)" R"("doubleValue":-55.3,"stringValue":"foo bar baz","enumValue":"BAR",)" R"("messageValue":{"value":2048},"repeatedBoolValue":[true],"repeatedInt32Value":[0,-42])" R"(,"repeatedUint64Value":["1","2"],"repeatedDoubleValue":[1.5,-2],)" R"("repeatedStringValue":["foo","bar ",""],"repeatedEnumValue":["BAR","FOO"],)" R"("repeatedMessageValue":[{"value":40},{},{"value":96}]})")); } TEST_P(JsonTest, CurseOfAtob) { auto m = ToProto(R"json( { repeatedBoolValue: ["0", "1", "false", "true", "f", "t", "no", "yes", "n", "y"] } )json"); ASSERT_OK(m); EXPECT_THAT(m->repeated_bool_value(), ElementsAre(false, true, false, true, false, true, false, true, false, true)); } TEST_P(JsonTest, FloatPrecision) { google::protobuf::Value v; v.mutable_list_value()->add_values()->set_number_value(0.9900000095367432); v.mutable_list_value()->add_values()->set_number_value(0.8799999952316284); EXPECT_THAT(ToJson(v), IsOkAndHolds("[0.99000000953674316,0.87999999523162842]")); } TEST_P(JsonTest, FloatMinMaxValue) { // 3.4028235e38 is FLT_MAX to 8-significant-digits. The final digit (5) // is rounded up; that means that when parsing this as a 64-bit FP number, // the value ends up higher than FLT_MAX. We still want to accept it though, // as a reasonable representation of FLT_MAX. auto m = ToProto(R"json( { "repeatedFloatValue": [3.4028235e38, -3.4028235e38], } )json"); ASSERT_OK(m); EXPECT_THAT(m->repeated_float_value(), ElementsAre(FLT_MAX, -FLT_MAX)); } TEST_P(JsonTest, FloatOutOfRange) { // Check that the slightly-lenient parsing demonstrated in FloatMinMaxValue // doesn't mean we allow all values. The value being parsed differs only // in the least significant (represented) digit. auto m = ToProto(R"json( { "floatValue": 3.4028236e38 } )json"); EXPECT_THAT(m, StatusIs(absl::StatusCode::kInvalidArgument)); } TEST_P(JsonTest, ParseLegacySingleRepeatedField) { auto m = ToProto(R"json({ "repeatedInt32Value": 1997, "repeatedStringValue": "oh no", "repeatedEnumValue": "BAR", "repeatedMessageValue": {"value": -1} })json"); ASSERT_OK(m); EXPECT_THAT(m->repeated_int32_value(), ElementsAre(1997)); EXPECT_THAT(m->repeated_string_value(), ElementsAre("oh no")); EXPECT_THAT(m->repeated_enum_value(), ElementsAre(proto3::EnumType::BAR)); ASSERT_THAT(m->repeated_message_value(), SizeIs(1)); EXPECT_EQ(m->repeated_message_value(0).value(), -1); EXPECT_THAT(ToJson(*m), IsOkAndHolds(R"({"repeatedInt32Value":[1997],)" R"("repeatedStringValue":["oh no"],)" R"("repeatedEnumValue":["BAR"],)" R"("repeatedMessageValue":[{"value":-1}]})")); } TEST_P(JsonTest, ParseMap) { TestMap message; (*message.mutable_string_map())["hello"] = 1234; auto printed = ToJson(message); ASSERT_THAT(printed, IsOkAndHolds(R"({"stringMap":{"hello":1234}})")); auto other = ToProto(*printed); ASSERT_OK(other); EXPECT_EQ(other->DebugString(), message.DebugString()); } TEST_P(JsonTest, RepeatedMapKey) { EXPECT_THAT(ToProto(R"json({ "string_map": { "twiceKey": 0, "twiceKey": 1 } })json"), StatusIs(absl::StatusCode::kInvalidArgument)); } TEST_P(JsonTest, ParsePrimitiveMapIn) { MapIn message; PrintOptions print_options; print_options.always_print_primitive_fields = true; auto printed = ToJson(message, print_options); ASSERT_THAT( ToJson(message, print_options), IsOkAndHolds(R"({"other":"","things":[],"mapInput":{},"mapAny":{}})")); auto other = ToProto(*printed); ASSERT_OK(other); EXPECT_EQ(other->DebugString(), message.DebugString()); } TEST_P(JsonTest, PrintPrimitiveOneof) { TestOneof message; PrintOptions options; options.always_print_primitive_fields = true; message.mutable_oneof_message_value(); EXPECT_THAT(ToJson(message, options), IsOkAndHolds(R"({"oneofMessageValue":{"value":0}})")); message.set_oneof_int32_value(1); EXPECT_THAT(ToJson(message, options), IsOkAndHolds(R"({"oneofInt32Value":1})")); } TEST_P(JsonTest, ParseOverOneof) { TestOneof m; m.set_oneof_string_value("foo"); ASSERT_OK(ToProto(m, R"json({ "oneofInt32Value": 5, })json")); EXPECT_EQ(m.oneof_int32_value(), 5); } TEST_P(JsonTest, RepeatedSingularKeys) { auto m = ToProto(R"json({ "int32Value": 1, "int32Value": 2 })json"); EXPECT_OK(m); EXPECT_EQ(m->int32_value(), 2); } TEST_P(JsonTest, RepeatedRepeatedKeys) { auto m = ToProto(R"json({ "repeatedInt32Value": [1], "repeatedInt32Value": [2, 3] })json"); EXPECT_OK(m); EXPECT_THAT(m->repeated_int32_value(), ElementsAre(1, 2, 3)); } TEST_P(JsonTest, RepeatedOneofKeys) { EXPECT_THAT(ToProto(R"json({ "oneofInt32Value": 1, "oneofStringValue": "foo" })json"), StatusIs(absl::StatusCode::kInvalidArgument)); } TEST_P(JsonTest, TestParseIgnoreUnknownFields) { ParseOptions options; options.ignore_unknown_fields = true; EXPECT_OK(ToProto(R"({"unknownName":0})", options)); TestMessage m; m.GetReflection()->MutableUnknownFields(&m)->AddFixed32(9001, 9001); m.GetReflection()->MutableUnknownFields(&m)->AddFixed64(9001, 9001); m.GetReflection()->MutableUnknownFields(&m)->AddVarint(9001, 9001); m.GetReflection()->MutableUnknownFields(&m)->AddLengthDelimited(9001, "9001"); EXPECT_THAT(ToJson(m), IsOkAndHolds("{}")); } TEST_P(JsonTest, TestParseErrors) { // Parsing should fail if the field name can not be recognized. EXPECT_THAT(ToProto(R"({"unknownName": 0})"), StatusIs(absl::StatusCode::kInvalidArgument)); // Parsing should fail if the value is invalid. EXPECT_THAT(ToProto(R"("{"int32Value": 2147483648})"), StatusIs(absl::StatusCode::kInvalidArgument)); } TEST_P(JsonTest, TestDynamicMessage) { // Create a new DescriptorPool with the same protos as the generated one. DescriptorPoolDatabase database(*DescriptorPool::generated_pool()); DescriptorPool pool(&database); // A dynamic version of the test proto. DynamicMessageFactory factory; std::unique_ptr message( factory.GetPrototype(pool.FindMessageTypeByName("proto3.TestMessage")) ->New()); ASSERT_OK(ToProto(*message, R"json( { "int32Value": 1024, "repeatedInt32Value": [1, 2], "messageValue": { "value": 2048 }, "repeatedMessageValue": [ {"value": 40}, {"value": 96} ] } )json")); // Convert to generated message for easy inspection. TestMessage generated; EXPECT_TRUE(generated.ParseFromString(message->SerializeAsString())); EXPECT_EQ(generated.int32_value(), 1024); EXPECT_THAT(generated.repeated_int32_value(), ElementsAre(1, 2)); EXPECT_EQ(generated.message_value().value(), 2048); ASSERT_EQ(generated.repeated_message_value_size(), 2); EXPECT_EQ(generated.repeated_message_value(0).value(), 40); EXPECT_EQ(generated.repeated_message_value(1).value(), 96); auto message_json = ToJson(*message); ASSERT_OK(message_json); auto generated_json = ToJson(generated); ASSERT_OK(generated_json); EXPECT_EQ(*message_json, *generated_json); } TEST_P(JsonTest, TestParsingAny) { auto m = ToProto(R"json( { "value": { "@type": "type.googleapis.com/proto3.TestMessage", "int32_value": 5, "string_value": "expected_value", "message_value": {"value": 1} } } )json"); ASSERT_OK(m); TestMessage t; ASSERT_TRUE(m->value().UnpackTo(&t)); EXPECT_EQ(t.int32_value(), 5); EXPECT_EQ(t.string_value(), "expected_value"); EXPECT_EQ(t.message_value().value(), 1); EXPECT_THAT( ToJson(*m), IsOkAndHolds( R"({"value":{"@type":"type.googleapis.com/proto3.TestMessage",)" R"("int32Value":5,"stringValue":"expected_value","messageValue":{"value":1}}})")); } TEST_P(JsonTest, TestParsingAnyWithRequiredFields) { auto m = ToProto(R"json( { "value": { "@type": "type.googleapis.com/protobuf_unittest.TestRequired", "a": 5, "dummy2": 6, "b": 7 } } )json"); ASSERT_OK(m); protobuf_unittest::TestRequired t; // Can't use UnpackTo directly, since that checks IsInitialized. ASSERT_FALSE(m->value().UnpackTo(&t)); t.Clear(); EXPECT_TRUE(t.ParsePartialFromString(m->value().value())); EXPECT_EQ(t.a(), 5); EXPECT_EQ(t.dummy2(), 6); EXPECT_EQ(t.b(), 7); EXPECT_FALSE(t.has_c()); EXPECT_THAT( ToJson(*m), IsOkAndHolds( R"({"value":{"@type":"type.googleapis.com/protobuf_unittest.TestRequired",)" R"("a":5,"dummy2":6,"b":7}})")); } TEST_P(JsonTest, TestParsingAnyEndAtType) { auto m = ToProto(R"json( { "value": { "int32_value": 5, "string_value": "expected_value", "message_value": {"value": 1}, "@type": "type.googleapis.com/proto3.TestMessage" } } )json"); ASSERT_OK(m); TestMessage t; ASSERT_TRUE(m->value().UnpackTo(&t)); EXPECT_EQ(t.int32_value(), 5); EXPECT_EQ(t.string_value(), "expected_value"); EXPECT_EQ(t.message_value().value(), 1); } TEST_P(JsonTest, TestParsingNestedAnys) { auto m = ToProto(R"json( { "value": { "value": { "int32_value": 5, "string_value": "expected_value", "message_value": {"value": 1}, "@type": "type.googleapis.com/proto3.TestMessage" }, "@type": "type.googleapis.com/google.protobuf.Any" } } )json"); ASSERT_OK(m); google::protobuf::Any inner; ASSERT_TRUE(m->value().UnpackTo(&inner)); TestMessage t; ASSERT_TRUE(inner.UnpackTo(&t)); EXPECT_EQ(t.int32_value(), 5); EXPECT_EQ(t.string_value(), "expected_value"); EXPECT_EQ(t.message_value().value(), 1); EXPECT_THAT( ToJson(*m), IsOkAndHolds( R"({"value":{"@type":"type.googleapis.com/google.protobuf.Any",)" R"("value":{"@type":"type.googleapis.com/proto3.TestMessage",)" R"("int32Value":5,"stringValue":"expected_value","messageValue":{"value":1}}}})")); } TEST_P(JsonTest, TestParsingBrokenAny) { auto m = ToProto(R"json( { "value": {} } )json"); ASSERT_OK(m); EXPECT_EQ(m->value().type_url(), ""); EXPECT_EQ(m->value().value(), ""); EXPECT_THAT(ToProto(R"json( { "value": { "type_url": "garbage", "value": "bW9yZSBnYXJiYWdl" } } )json"), StatusIs(absl::StatusCode::kInvalidArgument)); TestAny m2; m2.mutable_value(); EXPECT_THAT(ToJson(m2), IsOkAndHolds(R"({"value":{}})")); m2.mutable_value()->set_value("garbage"); // The ESF parser does not return InvalidArgument for this error. EXPECT_THAT(ToJson(m2), Not(StatusIs(absl::StatusCode::kOk))); m2.Clear(); m2.mutable_value()->set_type_url("type.googleapis.com/proto3.TestMessage"); EXPECT_THAT( ToJson(m2), IsOkAndHolds( R"({"value":{"@type":"type.googleapis.com/proto3.TestMessage"}})")); } TEST_P(JsonTest, TestFlatList) { auto m = ToProto(R"json( { "repeatedInt32Value": [[[5]], [6]] } )json"); ASSERT_OK(m); EXPECT_THAT(m->repeated_int32_value(), ElementsAre(5, 6)); // The above flatteing behavior is suppressed for google::protobuf::ListValue. auto m2 = ToProto(R"json( { "repeatedInt32Value": [[[5]], [6]] } )json"); ASSERT_OK(m2); auto fields = m2->struct_value().fields(); auto list = fields["repeatedInt32Value"].list_value(); EXPECT_EQ(list.values(0) .list_value() .values(0) .list_value() .values(0) .number_value(), 5); EXPECT_EQ(list.values(1).list_value().values(0).number_value(), 6); } TEST_P(JsonTest, ParseWrappers) { auto m = ToProto(R"json( { "boolValue": true, "int32Value": 42, "stringValue": "ieieo", } )json"); ASSERT_OK(m); EXPECT_TRUE(m->bool_value().value()); EXPECT_EQ(m->int32_value().value(), 42); EXPECT_EQ(m->string_value().value(), "ieieo"); EXPECT_THAT( ToJson(*m), IsOkAndHolds( R"({"boolValue":true,"int32Value":42,"stringValue":"ieieo"})")); auto m2 = ToProto(R"json( { "boolValue": { "value": true }, "int32Value": { "value": 42 }, "stringValue": { "value": "ieieo" }, } )json"); ASSERT_OK(m2); EXPECT_TRUE(m2->bool_value().value()); EXPECT_EQ(m2->int32_value().value(), 42); EXPECT_EQ(m2->string_value().value(), "ieieo"); } TEST_P(JsonTest, TestParsingUnknownAnyFields) { absl::string_view input = R"json( { "value": { "@type": "type.googleapis.com/proto3.TestMessage", "unknown_field": "UNKNOWN_VALUE", "string_value": "expected_value" } } )json"; EXPECT_THAT(ToProto(input), StatusIs(absl::StatusCode::kInvalidArgument)); ParseOptions options; options.ignore_unknown_fields = true; auto m = ToProto(input, options); ASSERT_OK(m); TestMessage t; ASSERT_TRUE(m->value().UnpackTo(&t)); EXPECT_EQ(t.string_value(), "expected_value"); } TEST_P(JsonTest, TestHugeBareString) { auto m = ToProto(R"json({ "int64Value": 6009652459062546621 })json"); ASSERT_OK(m); EXPECT_EQ(m->int64_value(), 6009652459062546621); } TEST_P(JsonTest, TestParsingUnknownEnumsProto2) { absl::string_view input = R"json({"ayuLmao": "UNKNOWN_VALUE"})json"; EXPECT_THAT(ToProto(input), StatusIs(absl::StatusCode::kInvalidArgument)); ParseOptions options; options.ignore_unknown_fields = true; auto m = ToProto(input, options); ASSERT_OK(m); EXPECT_FALSE(m->has_a()); } TEST_P(JsonTest, TestParsingUnknownEnumsProto3) { TestMessage m; absl::string_view input = R"json({"enum_value":"UNKNOWN_VALUE"})json"; m.set_enum_value(proto3::BAR); ASSERT_THAT(ToProto(m, input), StatusIs(absl::StatusCode::kInvalidArgument)); EXPECT_EQ(m.enum_value(), proto3::BAR); // Keep previous value ParseOptions options; options.ignore_unknown_fields = true; ASSERT_OK(ToProto(m, input, options)); EXPECT_EQ(m.enum_value(), 0); // Unknown enum value must be decoded as 0 } TEST_P(JsonTest, TestParsingUnknownEnumsProto3FromInt) { TestMessage m; absl::string_view input = R"json({"enum_value":12345})json"; m.set_enum_value(proto3::BAR); ASSERT_OK(ToProto(m, input)); EXPECT_EQ(m.enum_value(), 12345); ParseOptions options; options.ignore_unknown_fields = true; ASSERT_OK(ToProto(m, input, options)); EXPECT_EQ(m.enum_value(), 12345); } // Trying to pass an object as an enum field value is always treated as an // error TEST_P(JsonTest, TestParsingUnknownEnumsProto3FromObject) { absl::string_view input = R"json({"enum_value": {}})json"; EXPECT_THAT(ToProto(input), StatusIs(absl::StatusCode::kInvalidArgument)); ParseOptions options; options.ignore_unknown_fields = true; EXPECT_THAT(ToProto(input, options), StatusIs(absl::StatusCode::kInvalidArgument)); } TEST_P(JsonTest, TestParsingUnknownEnumsProto3FromArray) { absl::string_view input = R"json({"enum_value": []})json"; EXPECT_THAT(ToProto(input), StatusIs(absl::StatusCode::kInvalidArgument)); ParseOptions options; options.ignore_unknown_fields = true; EXPECT_THAT(ToProto(input, options), StatusIs(absl::StatusCode::kInvalidArgument)); } TEST_P(JsonTest, TestParsingRepeatedUnknownEnums) { absl::string_view input = R"json({ "repeated_enum_value": ["FOO", "BAZ", "BAR"] })json"; EXPECT_THAT(ToProto(input), StatusIs(absl::StatusCode::kInvalidArgument)); ParseOptions options; options.ignore_unknown_fields = true; auto m = ToProto(input, options); ASSERT_OK(m); EXPECT_THAT(m->repeated_enum_value(), ElementsAre(proto3::FOO, proto3::BAR)); } TEST_P(JsonTest, TestParsingEnumCaseSensitive) { TestMessage m; m.set_enum_value(proto3::FOO); EXPECT_THAT(ToProto(m, R"json({"enum_value": "bar"})json"), StatusIs(absl::StatusCode::kInvalidArgument)); // Default behavior is case-sensitive, so keep previous value. EXPECT_EQ(m.enum_value(), proto3::FOO); } TEST_P(JsonTest, TestParsingEnumLowercase) { ParseOptions options; options.case_insensitive_enum_parsing = true; auto m = ToProto(R"json({"enum_value": "TLSv1_2"})json", options); ASSERT_OK(m); EXPECT_THAT(m->enum_value(), proto3::TLSv1_2); } TEST_P(JsonTest, TestParsingEnumIgnoreCase) { TestMessage m; m.set_enum_value(proto3::FOO); ParseOptions options; options.case_insensitive_enum_parsing = true; ASSERT_OK(ToProto(m, R"json({"enum_value":"bar"})json", options)); EXPECT_EQ(m.enum_value(), proto3::BAR); } TEST_P(JsonTest, Extensions) { if (GetParam() == Codec::kResolver) { GTEST_SKIP(); } auto m = ToProto(R"json({ "[protobuf_unittest.TestMixedFieldsAndExtensions.c]": 42, "a": 5, "b": [1, 2, 3], "[protobuf_unittest.TestMixedFieldsAndExtensions.d]": [1, 1, 2, 3, 5, 8, 13] })json"); ASSERT_OK(m); EXPECT_EQ(m->a(), 5); EXPECT_THAT(m->b(), ElementsAre(1, 2, 3)); EXPECT_EQ(m->GetExtension(protobuf_unittest::TestMixedFieldsAndExtensions::c), 42); EXPECT_THAT( m->GetRepeatedExtension(protobuf_unittest::TestMixedFieldsAndExtensions::d), ElementsAre(1, 1, 2, 3, 5, 8, 13)); EXPECT_THAT( ToJson(*m), IsOkAndHolds( R"({"a":5,)" R"("[protobuf_unittest.TestMixedFieldsAndExtensions.c]":42,)" R"("b":[1,2,3],)" R"("[protobuf_unittest.TestMixedFieldsAndExtensions.d]":[1,1,2,3,5,8,13]})")); auto m2 = ToProto(R"json({ "[this.extension.does.not.exist]": 42 })json"); EXPECT_THAT(m2, StatusIs(absl::StatusCode::kInvalidArgument)); auto m3 = ToProto(R"json({ "[protobuf_unittest.TestMixedFieldsAndExtensions.c]": 42 })json"); EXPECT_THAT(m3, StatusIs(absl::StatusCode::kInvalidArgument)); } // Parsing does NOT work like MergeFrom: existing repeated field values are // clobbered, not appended to. TEST_P(JsonTest, TestOverwriteRepeated) { TestMessage m; m.add_repeated_int32_value(5); ASSERT_OK(ToProto(m, R"json({"repeated_int32_value": [1, 2, 3]})json")); EXPECT_THAT(m.repeated_int32_value(), ElementsAre(1, 2, 3)); } TEST_P(JsonTest, TestDuration) { auto m = ToProto(R"json( { "value": "123456.789s", "repeated_value": ["0.1s", "999s"] } )json"); ASSERT_OK(m); EXPECT_EQ(m->value().seconds(), 123456); EXPECT_EQ(m->value().nanos(), 789000000); EXPECT_THAT(m->repeated_value(), SizeIs(2)); EXPECT_EQ(m->repeated_value(0).seconds(), 0); EXPECT_EQ(m->repeated_value(0).nanos(), 100000000); EXPECT_EQ(m->repeated_value(1).seconds(), 999); EXPECT_EQ(m->repeated_value(1).nanos(), 0); EXPECT_THAT( ToJson(*m), IsOkAndHolds( R"({"value":"123456.789s","repeatedValue":["0.100s","999s"]})")); auto m2 = ToProto(R"json( { "value": {"seconds": 4, "nanos": 5}, } )json"); ASSERT_OK(m2); EXPECT_EQ(m2->value().seconds(), 4); EXPECT_EQ(m2->value().nanos(), 5); // Negative duration with zero seconds. auto m3 = ToProto(R"json( { "value": {"nanos": -5}, } )json"); ASSERT_OK(m3); EXPECT_EQ(m3->value().seconds(), 0); EXPECT_EQ(m3->value().nanos(), -5); EXPECT_THAT(ToJson(m3->value()), IsOkAndHolds("\"-0.000000005s\"")); // Negative duration with zero nanos. auto m4 = ToProto(R"json( { "value": {"seconds": -5}, } )json"); ASSERT_OK(m4); EXPECT_EQ(m4->value().seconds(), -5); EXPECT_EQ(m4->value().nanos(), 0); EXPECT_THAT(ToJson(m4->value()), IsOkAndHolds("\"-5s\"")); // Parse "0.5s" as a JSON string. auto m5 = ToProto(R"json( { "value": "0.5s", } )json"); ASSERT_OK(m5); EXPECT_EQ(m5->value().seconds(), 0); EXPECT_EQ(m5->value().nanos(), 500000000); EXPECT_THAT(ToJson(m5->value()), IsOkAndHolds("\"0.500s\"")); } // These tests are not exhaustive; tests in //google/protobuf/conformance // are more comprehensive. TEST_P(JsonTest, TestTimestamp) { auto m = ToProto(R"json( { "value": "1996-02-27T12:00:00Z", "repeated_value": ["9999-12-31T23:59:59Z"] } )json"); ASSERT_OK(m); EXPECT_EQ(m->value().seconds(), 825422400); EXPECT_EQ(m->value().nanos(), 0); EXPECT_THAT(m->repeated_value(), SizeIs(1)); EXPECT_EQ(m->repeated_value(0).seconds(), 253402300799); EXPECT_EQ(m->repeated_value(0).nanos(), 0); EXPECT_THAT( ToJson(*m), IsOkAndHolds( R"({"value":"1996-02-27T12:00:00Z","repeatedValue":["9999-12-31T23:59:59Z"]})")); auto m2 = ToProto(R"json( { "value": {"seconds": 4, "nanos": 5}, } )json"); ASSERT_OK(m2); EXPECT_EQ(m2->value().seconds(), 4); EXPECT_EQ(m2->value().nanos(), 5); } // This test case comes from Envoy's tests. They like to parse a Value out of // YAML, turn it into JSON, and then parse it as a different proto. This means // we must be extremely careful with integer fields, because they need to // round-trip through doubles. This happens all over Envoy. :( TEST_P(JsonTest, TestEnvoyRoundTrip) { auto m = ToProto(R"json( { "value": {"seconds": 1234567891, "nanos": 234000000}, } )json"); ASSERT_OK(m); auto j = ToJson(*m); ASSERT_OK(j); auto m2 = ToProto(*j); ASSERT_OK(m2); EXPECT_EQ(m2->value().seconds(), 1234567891); EXPECT_EQ(m2->value().nanos(), 234000000); } TEST_P(JsonTest, TestFieldMask) { auto m = ToProto(R"json( { "value": "foo,bar.bazBaz" } )json"); ASSERT_OK(m); EXPECT_THAT(m->value().paths(), ElementsAre("foo", "bar.baz_baz")); EXPECT_THAT(ToJson(*m), IsOkAndHolds(R"({"value":"foo,bar.bazBaz"})")); auto m2 = ToProto(R"json( { "value": { "paths": ["yep.really"] }, } )json"); ASSERT_OK(m2); EXPECT_THAT(m2->value().paths(), ElementsAre("yep.really")); } TEST_P(JsonTest, TestFieldMaskSnakeCase) { auto m = ToProto(R"json( { "value": "foo_bar" } )json"); ASSERT_OK(m); EXPECT_THAT(m->value().paths(), ElementsAre("foo_bar")); } TEST_P(JsonTest, TestLegalNullsInArray) { auto m = ToProto(R"json({ "repeatedNullValue": [null] })json"); ASSERT_OK(m); EXPECT_THAT(m->repeated_null_value(), ElementsAre(google::protobuf::NULL_VALUE)); auto m2 = ToProto(R"json({ "repeatedValue": [null] })json"); ASSERT_OK(m2); ASSERT_THAT(m2->repeated_value(), SizeIs(1)); EXPECT_TRUE(m2->repeated_value(0).has_null_value()); m2->Clear(); m2->mutable_value(); // Materialize an empty singular Value. m2->add_repeated_value(); m2->add_repeated_value()->set_string_value("solitude"); m2->add_repeated_value(); EXPECT_THAT(ToJson(*m2), IsOkAndHolds(R"({"repeatedValue":["solitude"]})")); } TEST_P(JsonTest, EmptyValue) { EXPECT_THAT(ToJson(google::protobuf::Value()), IsOkAndHolds("")); google::protobuf::Struct s; s.mutable_fields()->emplace("empty", google::protobuf::Value()); EXPECT_THAT(ToJson(s), IsOkAndHolds("{}")); } TEST_P(JsonTest, TrailingGarbage) { EXPECT_THAT(ToProto("{}garbage"), StatusIs(absl::StatusCode::kInvalidArgument)); } TEST_P(JsonTest, ListList) { auto m = ToProto(R"json({ "repeated_value": [["ayy", "lmao"]] })json"); ASSERT_OK(m); EXPECT_EQ(m->repeated_value(0).values(0).string_value(), "ayy"); EXPECT_EQ(m->repeated_value(0).values(1).string_value(), "lmao"); m = ToProto(R"json({ "repeated_value": [{ "values": ["ayy", "lmao"] }] })json"); ASSERT_OK(m); EXPECT_EQ(m->repeated_value(0).values(0).string_value(), "ayy"); EXPECT_EQ(m->repeated_value(0).values(1).string_value(), "lmao"); } TEST_P(JsonTest, HtmlEscape) { TestMessage m; m.set_string_value(""); EXPECT_THAT(ToJson(m), IsOkAndHolds(R"({"stringValue":"\u003c/script\u003e"})")); proto3::TestEvilJson m2; PrintOptions opts; opts.always_print_primitive_fields = true; EXPECT_THAT( ToJson(m2, opts), IsOkAndHolds( R"({"regular_name":0,"\u003c/script\u003e":0,)" R"("unbalanced\"quotes":0,)" R"("\"\u003cscript\u003ealert('hello!);\u003c/script\u003e":0})")); } TEST_P(JsonTest, FieldOrder) { // $ protoscope -s <<< "3: 3 22: 2 1: 1 22: 2" std::string out; absl::Status s = BinaryToJsonString( resolver_.get(), "type.googleapis.com/proto3.TestMessage", "\x18\x03\xb0\x01\x02\x08\x01\xb0\x01\x02", &out); ASSERT_OK(s); EXPECT_EQ( out, R"({"boolValue":true,"int64Value":"3","repeatedInt32Value":[2,2]})"); } // JSON values get special treatment when it comes to pre-existing values in // their repeated fields, when parsing through their dedicated syntax. TEST_P(JsonTest, ClearPreExistingRepeatedInJsonValues) { google::protobuf::ListValue l; l.add_values()->set_string_value("hello"); ASSERT_OK(JsonStringToMessage("[]", &l)); EXPECT_THAT(l.values(), IsEmpty()); google::protobuf::Struct s; (*s.mutable_fields())["hello"].set_string_value("world"); ASSERT_OK(JsonStringToMessage("{}", &s)); EXPECT_THAT(s.fields(), IsEmpty()); } } // namespace } // namespace json } // namespace protobuf } // namespace google