| // 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 google.protobuf.Value, ListValue and NullValue |
| // fields: every JSON kind a Value accepts, repeated Values and ListValues, how |
| // a NullValue is serialized inside and outside a oneof, the non-finite numbers |
| // a Value cannot express, and lists and Structs nested 25 deep (must be |
| // accepted) and 200 deep (must be rejected). This replaces the legacy |
| // BinaryAndJsonConformanceSuiteImpl<M>::RunJsonTestsForValue(), which ran for |
| // the proto3-style message types only; the test names and the requests sent to |
| // the testee are identical to the legacy ones, except that the binary-input |
| // tests below are BINARY_TEST requests (see below). |
| // |
| // 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; RunValidJsonTestWithValidator() |
| // asked for JSON output and inspected the JSON text. |
| // ExpectSerializeFailureForJson() sent the serialized text-format message as |
| // binary input, categorized as a JSON test, under |
| // "ProtobufInput.<name>.JsonOutput"; the same bytes are built here with the |
| // wire-format builders, and ParseBinary() categorizes them as BINARY_TEST, |
| // which no testee tells apart from JSON_TEST on a binary payload |
| // (b/563657722). |
| |
| #include <cstdint> |
| #include <limits> |
| #include <string> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "conformance/binary_test_util.h" |
| #include "conformance/binary_wireformat.h" |
| #include "conformance/json_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 JsonValueTest = MessageTypeConformanceTest; |
| |
| // Field numbers of the binary inputs of the serialize-failure tests: the |
| // test message's optional_value and google.protobuf.Value.number_value. |
| constexpr uint32_t kOptionalValue = 306; |
| constexpr uint32_t kValueNumberValue = 2; |
| |
| TEST_P(JsonValueTest, ValueAcceptInteger) { |
| constexpr absl::string_view kInput = R"json({"optionalValue": 1})json"; |
| constexpr absl::string_view kExpected = |
| R"pb(optional_value: { number_value: 1 })pb"; |
| EXPECT_THAT(Testee("ValueAcceptInteger") |
| .ParseJson(message(), kInput) |
| .SerializeBinary(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| EXPECT_THAT( |
| Testee("ValueAcceptInteger").ParseJson(message(), kInput).SerializeJson(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| } |
| |
| TEST_P(JsonValueTest, ValueAcceptFloat) { |
| constexpr absl::string_view kInput = R"json({"optionalValue": 1.5})json"; |
| constexpr absl::string_view kExpected = |
| R"pb(optional_value: { number_value: 1.5 })pb"; |
| EXPECT_THAT( |
| Testee("ValueAcceptFloat").ParseJson(message(), kInput).SerializeBinary(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| EXPECT_THAT( |
| Testee("ValueAcceptFloat").ParseJson(message(), kInput).SerializeJson(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| } |
| |
| TEST_P(JsonValueTest, ValueAcceptBool) { |
| constexpr absl::string_view kInput = R"json({"optionalValue": false})json"; |
| constexpr absl::string_view kExpected = |
| R"pb(optional_value: { bool_value: false })pb"; |
| EXPECT_THAT( |
| Testee("ValueAcceptBool").ParseJson(message(), kInput).SerializeBinary(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| EXPECT_THAT( |
| Testee("ValueAcceptBool").ParseJson(message(), kInput).SerializeJson(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| } |
| |
| TEST_P(JsonValueTest, ValueAcceptNull) { |
| constexpr absl::string_view kInput = R"json({"optionalValue": null})json"; |
| constexpr absl::string_view kExpected = |
| R"pb(optional_value: { null_value: NULL_VALUE })pb"; |
| EXPECT_THAT( |
| Testee("ValueAcceptNull").ParseJson(message(), kInput).SerializeBinary(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| EXPECT_THAT( |
| Testee("ValueAcceptNull").ParseJson(message(), kInput).SerializeJson(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| } |
| |
| TEST_P(JsonValueTest, ValueAcceptString) { |
| constexpr absl::string_view kInput = R"json({"optionalValue": "hello"})json"; |
| constexpr absl::string_view kExpected = |
| R"pb(optional_value: { string_value: "hello" })pb"; |
| EXPECT_THAT(Testee("ValueAcceptString") |
| .ParseJson(message(), kInput) |
| .SerializeBinary(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| EXPECT_THAT( |
| Testee("ValueAcceptString").ParseJson(message(), kInput).SerializeJson(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| } |
| |
| TEST_P(JsonValueTest, ValueAcceptList) { |
| constexpr absl::string_view kInput = |
| R"json({"optionalValue": [0, "hello"]})json"; |
| constexpr absl::string_view kExpected = R"pb( |
| optional_value: { |
| list_value: { |
| values: { number_value: 0 } |
| values: { string_value: "hello" } |
| } |
| } |
| )pb"; |
| EXPECT_THAT( |
| Testee("ValueAcceptList").ParseJson(message(), kInput).SerializeBinary(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| EXPECT_THAT( |
| Testee("ValueAcceptList").ParseJson(message(), kInput).SerializeJson(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| } |
| |
| TEST_P(JsonValueTest, ValueAcceptObject) { |
| constexpr absl::string_view kInput = |
| R"json({"optionalValue": {"value": 1}})json"; |
| constexpr absl::string_view kExpected = R"pb( |
| optional_value: { |
| struct_value: { |
| fields: { |
| key: "value" |
| value: { number_value: 1 } |
| } |
| } |
| } |
| )pb"; |
| EXPECT_THAT(Testee("ValueAcceptObject") |
| .ParseJson(message(), kInput) |
| .SerializeBinary(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| EXPECT_THAT( |
| Testee("ValueAcceptObject").ParseJson(message(), kInput).SerializeJson(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| } |
| |
| TEST_P(JsonValueTest, RepeatedValue) { |
| constexpr absl::string_view kInput = R"json({ |
| "repeatedValue": [["a"]] |
| })json"; |
| constexpr absl::string_view kExpected = R"pb( |
| repeated_value: |
| [ { |
| list_value: { |
| values: |
| [ { string_value: "a" }] |
| } |
| }] |
| )pb"; |
| EXPECT_THAT( |
| Testee("RepeatedValue").ParseJson(message(), kInput).SerializeBinary(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| EXPECT_THAT( |
| Testee("RepeatedValue").ParseJson(message(), kInput).SerializeJson(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| } |
| |
| TEST_P(JsonValueTest, RepeatedListValue) { |
| constexpr absl::string_view kInput = R"json({ |
| "repeatedListValue": [["a"]] |
| })json"; |
| constexpr absl::string_view kExpected = R"pb( |
| repeated_list_value: |
| [ { |
| values: |
| [ { string_value: "a" }] |
| }] |
| )pb"; |
| EXPECT_THAT(Testee("RepeatedListValue") |
| .ParseJson(message(), kInput) |
| .SerializeBinary(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| EXPECT_THAT( |
| Testee("RepeatedListValue").ParseJson(message(), kInput).SerializeJson(), |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); |
| } |
| |
| // A NullValue set in a oneof is emitted as `null`, whether it was given as |
| // the enum name or as `null`. |
| TEST_P(JsonValueTest, NullValueInOtherOneofOldFormat) { |
| EXPECT_THAT( |
| Testee(kP1, "NullValueInOtherOneofOldFormat") |
| .ParseJson(message(), R"json({"oneofNullValue": "NULL_VALUE"})json") |
| .SerializeJson(), |
| Yields(JsonPayload(HasJsonMemberThat("oneofNullValue", IsJsonNull())))); |
| } |
| |
| TEST_P(JsonValueTest, NullValueInOtherOneofNewFormat) { |
| EXPECT_THAT( |
| Testee(kP1, "NullValueInOtherOneofNewFormat") |
| .ParseJson(message(), R"json({"oneofNullValue": null})json") |
| .SerializeJson(), |
| Yields(JsonPayload(HasJsonMemberThat("oneofNullValue", IsJsonNull())))); |
| } |
| |
| // A NullValue field outside a oneof has no presence, so it is omitted. |
| TEST_P(JsonValueTest, NullValueInNormalMessage) { |
| EXPECT_THAT( |
| Testee(kP1, "NullValueInNormalMessage") |
| .ParseJson(message(), R"json({"optionalNullValue": null})json") |
| .SerializeJson(), |
| Yields(JsonPayload(IsEmptyJsonObject()))); |
| } |
| |
| // The binary form of the legacy input `optional_value: { number_value: nan}`. |
| TEST_P(JsonValueTest, ValueRejectNanNumberValue) { |
| EXPECT_THAT( |
| Testee(kP1, "ValueRejectNanNumberValue") |
| .ParseBinary( |
| message(), |
| LengthPrefixedField( |
| kOptionalValue, |
| DoubleField(kValueNumberValue, |
| std::numeric_limits<double>::quiet_NaN()))) |
| .SerializeJson(), |
| Yields(IsSerializeError())); |
| } |
| |
| // The binary form of the legacy input `optional_value: { number_value: inf}`. |
| TEST_P(JsonValueTest, ValueRejectInfNumberValue) { |
| EXPECT_THAT(Testee(kP1, "ValueRejectInfNumberValue") |
| .ParseBinary( |
| message(), |
| LengthPrefixedField( |
| kOptionalValue, |
| DoubleField(kValueNumberValue, |
| std::numeric_limits<double>::infinity()))) |
| .SerializeJson(), |
| Yields(IsSerializeError())); |
| } |
| |
| TEST_P(JsonValueTest, ListValueDeepNesting25) { |
| const std::string input = |
| absl::StrCat(R"({"optionalValue": )", std::string(25, '['), "1", |
| std::string(25, ']'), "}"); |
| const std::string expected = absl::StrCat("optional_value: {\n", |
| Repeat(" list_value: {\n" |
| " values: {\n", |
| 25), |
| " number_value: 1\n", |
| Repeat(" }\n" |
| " }\n", |
| 25), |
| "}\n"); |
| EXPECT_THAT(Testee(kP1, "ListValueDeepNesting25") |
| .ParseJson(message(), input) |
| .SerializeBinary(), |
| Yields(WhenParsed(EqualsTextProto(expected)))); |
| EXPECT_THAT(Testee(kP1, "ListValueDeepNesting25") |
| .ParseJson(message(), input) |
| .SerializeJson(), |
| Yields(WhenParsed(EqualsTextProto(expected)))); |
| } |
| |
| TEST_P(JsonValueTest, ListValueDeepNesting200) { |
| const std::string input = |
| absl::StrCat(R"({"optionalValue": )", std::string(200, '['), "1", |
| std::string(200, ']'), "}"); |
| EXPECT_THAT(Testee(kP1, "ListValueDeepNesting200") |
| .ParseJson(message(), input) |
| .ParseOnly(), |
| Yields(IsParseError())); |
| } |
| |
| TEST_P(JsonValueTest, ValueDeepNesting25) { |
| const std::string input = |
| absl::StrCat(R"({"optionalValue": {)", Repeat(R"("n": {)", 25), |
| R"("value": 1)", std::string(25, '}'), "}}"); |
| const std::string expected = |
| absl::StrCat("optional_value: {\n", " struct_value: {\n", |
| Repeat(" fields: {\n" |
| " key: \"n\"\n" |
| " value: {\n" |
| " struct_value: {\n", |
| 25), |
| " fields: {\n" |
| " key: \"value\"\n" |
| " value: { number_value: 1 }\n" |
| " }\n", |
| Repeat(" }\n" |
| " }\n" |
| " }\n", |
| 25), |
| " }\n", "}\n"); |
| EXPECT_THAT(Testee(kP1, "ValueDeepNesting25") |
| .ParseJson(message(), input) |
| .SerializeBinary(), |
| Yields(WhenParsed(EqualsTextProto(expected)))); |
| EXPECT_THAT(Testee(kP1, "ValueDeepNesting25") |
| .ParseJson(message(), input) |
| .SerializeJson(), |
| Yields(WhenParsed(EqualsTextProto(expected)))); |
| } |
| |
| TEST_P(JsonValueTest, ValueDeepNesting200) { |
| const std::string input = |
| absl::StrCat(R"({"optionalValue": {)", Repeat(R"("n": {)", 200), |
| R"("value": 1)", std::string(200, '}'), "}}"); |
| EXPECT_THAT(Testee(kP1, "ValueDeepNesting200") |
| .ParseJson(message(), input) |
| .ParseOnly(), |
| Yields(IsParseError())); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, JsonValueTest, ValuesIn(Proto3TestMessageTypes()), |
| MessageTypeParamName); |
| |
| } // namespace |
| } // namespace conformance |
| } // namespace protobuf |
| } // namespace google |