| // 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 string and bytes fields: Unicode text, JSON | |
| // escapes (including \u escapes, surrogate pairs and embedded NUL), rejected | |
| // malformed escapes, unpaired surrogates, non-string values and single-quoted | |
| // strings, and base64 / base64url bytes. This replaces the "String fields" | |
| // and "Bytes fields" blocks (and the single-quote tests at the end) of the | |
| // legacy | |
| // BinaryAndJsonConformanceSuiteImpl<M>::RunJsonTestsForNonRepeatedTypes(); 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; | |
| // --------------------------------------------------------------------------- | |
| // String fields. | |
| // --------------------------------------------------------------------------- | |
| using JsonStringTest = MessageTypeConformanceTest; | |
| TEST_P(JsonStringTest, StringField) { | |
| constexpr absl::string_view kInput = | |
| R"json({"optionalString": "Hello world!"})json"; | |
| constexpr absl::string_view kExpected = | |
| R"pb(optional_string: "Hello world!")pb"; | |
| EXPECT_THAT( | |
| Testee("StringField").ParseJson(message(), kInput).SerializeBinary(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| EXPECT_THAT( | |
| Testee("StringField").ParseJson(message(), kInput).SerializeJson(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| } | |
| // Non-ASCII characters in the JSON text itself, as opposed to the \uXXXX | |
| // escapes tested below. Note that kInput is deliberately not a raw string | |
| // literal: the compiler resolves the \x escapes, so the payload holds the | |
| // UTF-8 bytes of U+8C37 U+6B4C ("Google" in Chinese). In the raw string | |
| // literals below, the \u escapes reach the testee's JSON parser verbatim. | |
| TEST_P(JsonStringTest, StringFieldUnicode) { | |
| constexpr absl::string_view kInput = | |
| "{\"optionalString\": \"\xE8\xB0\xB7\xE6\xAD\x8C\"}"; | |
| constexpr absl::string_view kExpected = | |
| R"pb(optional_string: "\xE8\xB0\xB7\xE6\xAD\x8C")pb"; | |
| EXPECT_THAT(Testee("StringFieldUnicode") | |
| .ParseJson(message(), kInput) | |
| .SerializeBinary(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| EXPECT_THAT( | |
| Testee("StringFieldUnicode").ParseJson(message(), kInput).SerializeJson(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| } | |
| TEST_P(JsonStringTest, StringFieldEscape) { | |
| constexpr absl::string_view kInput = | |
| R"json({"optionalString": "\"\\\/\b\f\n\r\t"})json"; | |
| constexpr absl::string_view kExpected = | |
| R"pb(optional_string: "\"\\/\b\f\n\r\t")pb"; | |
| EXPECT_THAT(Testee("StringFieldEscape") | |
| .ParseJson(message(), kInput) | |
| .SerializeBinary(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| EXPECT_THAT( | |
| Testee("StringFieldEscape").ParseJson(message(), kInput).SerializeJson(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| } | |
| TEST_P(JsonStringTest, StringFieldUnicodeEscape) { | |
| constexpr absl::string_view kInput = | |
| R"json({"optionalString": "\u8C37\u6B4C"})json"; | |
| constexpr absl::string_view kExpected = | |
| R"pb(optional_string: "\xE8\xB0\xB7\xE6\xAD\x8C")pb"; | |
| EXPECT_THAT(Testee("StringFieldUnicodeEscape") | |
| .ParseJson(message(), kInput) | |
| .SerializeBinary(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| EXPECT_THAT(Testee("StringFieldUnicodeEscape") | |
| .ParseJson(message(), kInput) | |
| .SerializeJson(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| } | |
| // U+1F601 (grinning face with smiling eyes). | |
| TEST_P(JsonStringTest, StringFieldSurrogatePair) { | |
| constexpr absl::string_view kInput = | |
| R"json({"optionalString": "\uD83D\uDE01"})json"; | |
| constexpr absl::string_view kExpected = | |
| R"pb(optional_string: "\xF0\x9F\x98\x81")pb"; | |
| EXPECT_THAT(Testee("StringFieldSurrogatePair") | |
| .ParseJson(message(), kInput) | |
| .SerializeBinary(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| EXPECT_THAT(Testee("StringFieldSurrogatePair") | |
| .ParseJson(message(), kInput) | |
| .SerializeJson(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| } | |
| TEST_P(JsonStringTest, StringFieldEmbeddedNull) { | |
| constexpr absl::string_view kInput = | |
| R"json({"optionalString": "Hello\u0000world!"})json"; | |
| constexpr absl::string_view kExpected = | |
| R"pb(optional_string: "Hello\000world!")pb"; | |
| EXPECT_THAT(Testee("StringFieldEmbeddedNull") | |
| .ParseJson(message(), kInput) | |
| .SerializeBinary(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| EXPECT_THAT(Testee("StringFieldEmbeddedNull") | |
| .ParseJson(message(), kInput) | |
| .SerializeJson(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| } | |
| // Unicode escapes must start with "\u" (lowercase u). | |
| TEST_P(JsonStringTest, StringFieldUppercaseEscapeLetter) { | |
| EXPECT_THAT( | |
| Testee(kP1, "StringFieldUppercaseEscapeLetter") | |
| .ParseJson(message(), "{\"optionalString\": \"\\U8C37\\U6b4C\"}") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonStringTest, StringFieldInvalidEscape) { | |
| EXPECT_THAT( | |
| Testee(kP1, "StringFieldInvalidEscape") | |
| .ParseJson(message(), "{\"optionalString\": \"\\uXXXX\\u6B4C\"}") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonStringTest, StringFieldUnterminatedEscape) { | |
| EXPECT_THAT( | |
| Testee(kP1, "StringFieldUnterminatedEscape") | |
| .ParseJson(message(), R"json({"optionalString": "\u8C3"})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| // The unpaired-surrogate inputs below are ordinary (not raw) string literals: | |
| // MSVC rejects a lone surrogate written as \uXXXX even inside a raw string | |
| // literal (error C3850), so the backslash is escaped to keep the \u escape | |
| // out of the compiler's hands and deliver it to the testee's JSON parser. | |
| TEST_P(JsonStringTest, StringFieldUnpairedHighSurrogate) { | |
| EXPECT_THAT(Testee(kP1, "StringFieldUnpairedHighSurrogate") | |
| .ParseJson(message(), "{\"optionalString\": \"\\uD800\"}") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonStringTest, StringFieldUnpairedLowSurrogate) { | |
| EXPECT_THAT(Testee(kP1, "StringFieldUnpairedLowSurrogate") | |
| .ParseJson(message(), "{\"optionalString\": \"\\uDC00\"}") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonStringTest, StringFieldSurrogateInWrongOrder) { | |
| EXPECT_THAT( | |
| Testee(kP1, "StringFieldSurrogateInWrongOrder") | |
| .ParseJson(message(), "{\"optionalString\": \"\\uDE01\\uD83D\"}") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonStringTest, StringFieldNotAString) { | |
| EXPECT_THAT(Testee("StringFieldNotAString") | |
| .ParseJson(message(), R"json({"optionalString": 12345})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| // http://www.rfc-editor.org/rfc/rfc7159.txt says strings have to use double | |
| // quotes. | |
| TEST_P(JsonStringTest, StringFieldSingleQuoteKey) { | |
| EXPECT_THAT( | |
| Testee(kP1, "StringFieldSingleQuoteKey") | |
| .ParseJson(message(), R"json({'optionalString': "Hello world!"})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonStringTest, StringFieldSingleQuoteValue) { | |
| EXPECT_THAT( | |
| Testee(kP1, "StringFieldSingleQuoteValue") | |
| .ParseJson(message(), R"json({"optionalString": 'Hello world!'})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| TEST_P(JsonStringTest, StringFieldSingleQuoteBoth) { | |
| EXPECT_THAT( | |
| Testee(kP1, "StringFieldSingleQuoteBoth") | |
| .ParseJson(message(), R"json({'optionalString': 'Hello world!'})json") | |
| .ParseOnly(), | |
| Yields(IsParseError())); | |
| } | |
| INSTANTIATE_TEST_SUITE_P(All, JsonStringTest, ValuesIn(AllTestMessageTypes()), | |
| MessageTypeParamName); | |
| // --------------------------------------------------------------------------- | |
| // Bytes fields. | |
| // --------------------------------------------------------------------------- | |
| using JsonBytesTest = MessageTypeConformanceTest; | |
| TEST_P(JsonBytesTest, BytesField) { | |
| constexpr absl::string_view kInput = R"json({"optionalBytes": "AQI="})json"; | |
| constexpr absl::string_view kExpected = R"pb(optional_bytes: "\x01\x02")pb"; | |
| EXPECT_THAT( | |
| Testee("BytesField").ParseJson(message(), kInput).SerializeBinary(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| EXPECT_THAT(Testee("BytesField").ParseJson(message(), kInput).SerializeJson(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| } | |
| TEST_P(JsonBytesTest, BytesFieldBase64Url) { | |
| constexpr absl::string_view kInput = R"json({"optionalBytes": "-_"})json"; | |
| constexpr absl::string_view kExpected = R"pb(optional_bytes: "\xfb")pb"; | |
| EXPECT_THAT(Testee(kP1, "BytesFieldBase64Url") | |
| .ParseJson(message(), kInput) | |
| .SerializeBinary(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| EXPECT_THAT(Testee(kP1, "BytesFieldBase64Url") | |
| .ParseJson(message(), kInput) | |
| .SerializeJson(), | |
| Yields(WhenParsed(EqualsTextProto(kExpected)))); | |
| } | |
| INSTANTIATE_TEST_SUITE_P(All, JsonBytesTest, ValuesIn(AllTestMessageTypes()), | |
| MessageTypeParamName); | |
| } // namespace | |
| } // namespace conformance | |
| } // namespace protobuf | |
| } // namespace google |