blob: 403dfe0a6799e1b6cdad16cb250635161324c321 [file] [edit]
// 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 `null`: it is accepted (and means "unset") as
// the value of a field of any type, but not as an element of a repeated
// field or as a map key or value. This replaces the legacy
// BinaryAndJsonConformanceSuiteImpl<M>::RunJsonTestsForNullTypes(), for
// every test message type; the test names and the requests sent to the
// testee are identical to the legacy ones.
//
// Like the legacy RunValidJsonTest(), the valid input is sent twice: once to
// be serialized as binary ("<name>.ProtobufOutput") and once as JSON
// ("<name>.JsonOutput"). The legacy 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;
class JsonNullTest : public MessageTypeConformanceTest {
private:
TestPriority DefaultPriority() const override { return kP1; }
};
// "null" is accepted for all field types, and leaves the field unset.
TEST_P(JsonNullTest, AllFieldAcceptNull) {
constexpr absl::string_view kInput = R"json({
"optionalInt32": null,
"optionalInt64": null,
"optionalUint32": null,
"optionalUint64": null,
"optionalSint32": null,
"optionalSint64": null,
"optionalFixed32": null,
"optionalFixed64": null,
"optionalSfixed32": null,
"optionalSfixed64": null,
"optionalFloat": null,
"optionalDouble": null,
"optionalBool": null,
"optionalString": null,
"optionalBytes": null,
"optionalNestedEnum": null,
"optionalNestedMessage": null,
"repeatedInt32": null,
"repeatedInt64": null,
"repeatedUint32": null,
"repeatedUint64": null,
"repeatedSint32": null,
"repeatedSint64": null,
"repeatedFixed32": null,
"repeatedFixed64": null,
"repeatedSfixed32": null,
"repeatedSfixed64": null,
"repeatedFloat": null,
"repeatedDouble": null,
"repeatedBool": null,
"repeatedString": null,
"repeatedBytes": null,
"repeatedNestedEnum": null,
"repeatedNestedMessage": null,
"mapInt32Int32": null,
"mapBoolBool": null,
"mapStringNestedMessage": null
})json";
EXPECT_THAT(Testee(kP0, "AllFieldAcceptNull")
.ParseJson(message(), kInput)
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(""))));
EXPECT_THAT(Testee(kP0, "AllFieldAcceptNull")
.ParseJson(message(), kInput)
.SerializeJson(),
Yields(WhenParsed(EqualsTextProto(""))));
}
// Repeated field elements cannot be null.
TEST_P(JsonNullTest, RepeatedFieldPrimitiveElementIsNull) {
EXPECT_THAT(
Testee("RepeatedFieldPrimitiveElementIsNull")
.ParseJson(message(), R"json({"repeatedInt32": [1, null, 2]})json")
.ParseOnly(),
Yields(IsParseError()));
}
TEST_P(JsonNullTest, RepeatedFieldMessageElementIsNull) {
EXPECT_THAT(
Testee("RepeatedFieldMessageElementIsNull")
.ParseJson(
message(),
R"json({"repeatedNestedMessage": [{"a":1}, null, {"a":2}]})json")
.ParseOnly(),
Yields(IsParseError()));
}
// Map field keys cannot be null.
TEST_P(JsonNullTest, MapFieldKeyIsNull) {
EXPECT_THAT(
Testee("MapFieldKeyIsNull")
.ParseJson(message(), R"json({"mapInt32Int32": {null: 1}})json")
.ParseOnly(),
Yields(IsParseError()));
}
// Map field values cannot be null.
TEST_P(JsonNullTest, MapFieldValueIsNull) {
EXPECT_THAT(
Testee("MapFieldValueIsNull")
.ParseJson(message(), R"json({"mapInt32Int32": {"0": null}})json")
.ParseOnly(),
Yields(IsParseError()));
}
INSTANTIATE_TEST_SUITE_P(All, JsonNullTest, ValuesIn(AllTestMessageTypes()),
MessageTypeParamName);
} // namespace
} // namespace conformance
} // namespace protobuf
} // namespace google