| // Protocol Buffers - Google's data interchange format | |
| // Copyright 2026 Google LLC. All rights reserved. | |
| // | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file or at | |
| // https://developers.google.com/open-source/licenses/bsd | |
| // JSON conformance tests for repeated fields: JSON arrays of primitives, | |
| // enums, strings, bytes and messages round-trip, array elements of the wrong | |
| // type are rejected, a single value where an array was expected is rejected, | |
| // and so is a trailing comma in an array. This replaces the legacy | |
| // BinaryAndJsonConformanceSuiteImpl<M>::RunJsonTestsForRepeatedTypes(); the | |
| // test names and the requests sent to the testee are identical to the legacy | |
| // ones. | |
| // | |
| // Like the legacy RunValidJsonTest(), each valid input is sent twice: once to | |
| // be serialized as binary ("<name>.ProtobufOutput") and once as JSON | |
| // ("<name>.JsonOutput"). ExpectParseFailureForJson() asked for JSON output | |
| // under a name without an output suffix. | |
| #include <gmock/gmock.h> | |
| #include <gtest/gtest.h> | |
| #include "absl/strings/string_view.h" | |
| #include "conformance/binary_test_util.h" | |
| #include "conformance/matchers.h" | |
| #include "conformance/message_type_fixtures.h" | |
| #include "conformance/testee.h" | |
| namespace google { | |
| namespace protobuf { | |
| namespace conformance { | |
| namespace { | |
| using ::testing::ValuesIn; | |
| using JsonRepeatedTest = MessageTypeConformanceTest; | |
| // --------------------------------------------------------------------------- | |
| // Repeated fields of each kind round-trip. | |
| // --------------------------------------------------------------------------- | |
| TEST_P(JsonRepeatedTest, PrimitiveRepeatedField) { | |
| constexpr absl::string_view kInput = | |
| R"json({"repeatedInt32": [1, 2, 3, 4]})json"; | |
| constexpr absl::string_view kExpected = | |
| R"pb(repeated_int32: [ 1, 2, 3, 4 ])pb"; | |
| EXPECT_THAT(Testee("PrimitiveRepeatedField") | |
| .ParseJson(message(), kInput) | |
| .SerializeBinary(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| EXPECT_THAT(Testee("PrimitiveRepeatedField") | |
| .ParseJson(message(), kInput) | |
| .SerializeJson(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| } | |
| TEST_P(JsonRepeatedTest, EnumRepeatedField) { | |
| constexpr absl::string_view kInput = | |
| R"json({"repeatedNestedEnum": ["FOO", "BAR", "BAZ"]})json"; | |
| constexpr absl::string_view kExpected = | |
| R"pb(repeated_nested_enum: [ FOO, BAR, BAZ ])pb"; | |
| EXPECT_THAT(Testee("EnumRepeatedField") | |
| .ParseJson(message(), kInput) | |
| .SerializeBinary(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| EXPECT_THAT( | |
| Testee("EnumRepeatedField").ParseJson(message(), kInput).SerializeJson(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| } | |
| TEST_P(JsonRepeatedTest, StringRepeatedField) { | |
| constexpr absl::string_view kInput = | |
| R"json({"repeatedString": ["Hello", "world"]})json"; | |
| constexpr absl::string_view kExpected = | |
| R"pb(repeated_string: [ "Hello", "world" ])pb"; | |
| EXPECT_THAT(Testee("StringRepeatedField") | |
| .ParseJson(message(), kInput) | |
| .SerializeBinary(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| EXPECT_THAT(Testee("StringRepeatedField") | |
| .ParseJson(message(), kInput) | |
| .SerializeJson(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| } | |
| TEST_P(JsonRepeatedTest, BytesRepeatedField) { | |
| constexpr absl::string_view kInput = | |
| R"json({"repeatedBytes": ["AAEC", "AQI="]})json"; | |
| constexpr absl::string_view kExpected = | |
| R"pb(repeated_bytes: [ "\x00\x01\x02", "\x01\x02" ])pb"; | |
| EXPECT_THAT(Testee("BytesRepeatedField") | |
| .ParseJson(message(), kInput) | |
| .SerializeBinary(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| EXPECT_THAT( | |
| Testee("BytesRepeatedField").ParseJson(message(), kInput).SerializeJson(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| } | |
| TEST_P(JsonRepeatedTest, MessageRepeatedField) { | |
| constexpr absl::string_view kInput = | |
| R"json({"repeatedNestedMessage": [{"a": 1234}, {"a": 5678}]})json"; | |
| constexpr absl::string_view kExpected = R"pb( | |
| repeated_nested_message: { a: 1234 } | |
| repeated_nested_message: { a: 5678 } | |
| )pb"; | |
| EXPECT_THAT(Testee("MessageRepeatedField") | |
| .ParseJson(message(), kInput) | |
| .SerializeBinary(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| EXPECT_THAT(Testee("MessageRepeatedField") | |
| .ParseJson(message(), kInput) | |
| .SerializeJson(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Repeated field elements of the wrong type are rejected. | |
| // --------------------------------------------------------------------------- | |
| TEST_P(JsonRepeatedTest, | |
| RepeatedFieldWrongElementTypeExpectingIntegersGotBool) { | |
| EXPECT_THAT(Testee("RepeatedFieldWrongElementTypeExpectingIntegersGotBool") | |
| .ParseJson(message(), | |
| R"json({"repeatedInt32": [1, false, 3, 4]})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonRepeatedTest, | |
| RepeatedFieldWrongElementTypeExpectingIntegersGotString) { | |
| EXPECT_THAT(Testee("RepeatedFieldWrongElementTypeExpectingIntegersGotString") | |
| .ParseJson(message(), | |
| R"json({"repeatedInt32": [1, 2, "name", 4]})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonRepeatedTest, | |
| RepeatedFieldWrongElementTypeExpectingIntegersGotMessage) { | |
| EXPECT_THAT( | |
| Testee("RepeatedFieldWrongElementTypeExpectingIntegersGotMessage") | |
| .ParseJson(message(), | |
| R"json({"repeatedInt32": [1, 2, 3, {"a": 4}]})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonRepeatedTest, RepeatedFieldWrongElementTypeExpectingStringsGotInt) { | |
| EXPECT_THAT( | |
| Testee("RepeatedFieldWrongElementTypeExpectingStringsGotInt") | |
| .ParseJson(message(), | |
| R"json({"repeatedString": ["1", 2, "3", "4"]})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonRepeatedTest, RepeatedFieldWrongElementTypeExpectingStringsGotBool) { | |
| EXPECT_THAT( | |
| Testee("RepeatedFieldWrongElementTypeExpectingStringsGotBool") | |
| .ParseJson(message(), | |
| R"json({"repeatedString": ["1", "2", false, "4"]})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonRepeatedTest, | |
| RepeatedFieldWrongElementTypeExpectingStringsGotMessage) { | |
| EXPECT_THAT( | |
| Testee("RepeatedFieldWrongElementTypeExpectingStringsGotMessage") | |
| .ParseJson(message(), | |
| R"json({"repeatedString": ["1", 2, "3", {"a": 4}]})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonRepeatedTest, RepeatedFieldWrongElementTypeExpectingMessagesGotInt) { | |
| EXPECT_THAT( | |
| Testee("RepeatedFieldWrongElementTypeExpectingMessagesGotInt") | |
| .ParseJson(message(), | |
| R"json({"repeatedNestedMessage": [{"a": 1}, 2]})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonRepeatedTest, | |
| RepeatedFieldWrongElementTypeExpectingMessagesGotBool) { | |
| EXPECT_THAT( | |
| Testee("RepeatedFieldWrongElementTypeExpectingMessagesGotBool") | |
| .ParseJson(message(), | |
| R"json({"repeatedNestedMessage": [{"a": 1}, false]})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonRepeatedTest, | |
| RepeatedFieldWrongElementTypeExpectingMessagesGotString) { | |
| EXPECT_THAT( | |
| Testee("RepeatedFieldWrongElementTypeExpectingMessagesGotString") | |
| .ParseJson(message(), | |
| R"json({"repeatedNestedMessage": [{"a": 1}, "2"]})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| // --------------------------------------------------------------------------- | |
| // A single value where a repeated field was expected is rejected, even if it | |
| // is of the right type. | |
| // --------------------------------------------------------------------------- | |
| TEST_P(JsonRepeatedTest, SingleValueForRepeatedFieldInt32) { | |
| EXPECT_THAT(Testee("SingleValueForRepeatedFieldInt32") | |
| .ParseJson(message(), R"json({"repeatedInt32": 1})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonRepeatedTest, SingleValueForRepeatedFieldMessage) { | |
| EXPECT_THAT(Testee("SingleValueForRepeatedFieldMessage") | |
| .ParseJson(message(), | |
| R"json({"repeatedNestedMessage": {"a": 1}})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| // --------------------------------------------------------------------------- | |
| // A trailing comma in a repeated field is rejected. | |
| // --------------------------------------------------------------------------- | |
| TEST_P(JsonRepeatedTest, RepeatedFieldTrailingComma) { | |
| EXPECT_THAT( | |
| Testee(kP1, "RepeatedFieldTrailingComma") | |
| .ParseJson(message(), R"json({"repeatedInt32": [1, 2, 3, 4,]})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonRepeatedTest, RepeatedFieldTrailingCommaWithSpace) { | |
| EXPECT_THAT( | |
| Testee(kP1, "RepeatedFieldTrailingCommaWithSpace") | |
| .ParseJson(message(), R"json({"repeatedInt32": [1, 2, 3, 4 ,]})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonRepeatedTest, RepeatedFieldTrailingCommaWithSpaceCommaSpace) { | |
| EXPECT_THAT( | |
| Testee(kP1, "RepeatedFieldTrailingCommaWithSpaceCommaSpace") | |
| .ParseJson(message(), R"json({"repeatedInt32": [1, 2, 3, 4 , ]})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonRepeatedTest, RepeatedFieldTrailingCommaWithNewlines) { | |
| EXPECT_THAT( | |
| Testee(kP1, "RepeatedFieldTrailingCommaWithNewlines") | |
| .ParseJson(message(), | |
| "{\"repeatedInt32\": [\n 1,\n 2,\n 3,\n 4,\n]}") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| INSTANTIATE_TEST_SUITE_P(All, JsonRepeatedTest, ValuesIn(AllTestMessageTypes()), | |
| MessageTypeParamName); | |
| } // namespace | |
| } // namespace conformance | |
| } // namespace protobuf | |
| } // namespace google |