Skip to content

Commit

Permalink
Add new 'always_print_without_presence_fields' option to the C++ JSON…
Browse files Browse the repository at this point in the history
… serializer.

This flag has consistent behavior between proto2 and proto3 optionals (by not including either one), unlike always_print_primitive_fields which does include proto2 optional but excludes proto3 optionals.

always_print_primitive_fields is now deprecated and will be removed in an upcoming release.

PiperOrigin-RevId: 603362526
  • Loading branch information
protobuf-github-bot authored and Copybara-Service committed Feb 1, 2024
1 parent 4687ef3 commit 671b61b
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 58 deletions.
8 changes: 6 additions & 2 deletions src/google/protobuf/json/internal/unparser.cc
Expand Up @@ -433,8 +433,9 @@ absl::Status WriteField(JsonWriter& writer, const Msg<Traits>& msg,
} else if (Traits::IsRepeated(field)) {
return WriteRepeated<Traits>(writer, msg, field);
} else if (Traits::GetSize(field, msg) == 0) {
// We can only get here if always_print_primitive_fields is true.
ABSL_DCHECK(writer.options().always_print_primitive_fields);
// We can only get here if one of the always_print options is true.
ABSL_DCHECK(writer.options().always_print_primitive_fields ||
writer.options().always_print_without_presence_fields);

if (Traits::FieldType(field) == FieldDescriptor::TYPE_GROUP) {
// We do not yet have full group support, but this is required so that we
Expand Down Expand Up @@ -464,6 +465,9 @@ absl::Status WriteFields(JsonWriter& writer, const Msg<Traits>& msg,
Traits::FieldType(field) == FieldDescriptor::TYPE_MESSAGE;
has |= !is_singular_message && !Traits::IsOneof(field);
}
if (writer.options().always_print_without_presence_fields) {
has |= Traits::IsRepeated(field) || Traits::IsImplicitPresence(field);
}

if (has) {
fields.push_back(field);
Expand Down
18 changes: 14 additions & 4 deletions src/google/protobuf/json/internal/writer.h
Expand Up @@ -36,11 +36,21 @@ struct WriterOptions {
// Whether to add spaces, line breaks and indentation to make the JSON output
// easy to read.
bool add_whitespace = false;
// Whether to always print primitive fields. By default proto3 primitive
// fields with default values will be omitted in JSON output. For example, an
// int32 field set to 0 will be omitted. Set this flag to true will override
// the default behavior and print primitive fields regardless of their values.
// Whether to always print the following types of fields even if they would
// otherwise be omitted:
// - Implicit presence fields set to their 0 value
// - Empty lists and maps
// - Proto2 optional and required scalar fields which are not present (but not
// Proto3 optional scalar fields).
// Note: This option is deprecated in favor of
// always_print_without_presence_fields which treats proto2 and proto3
// optionals the same and will be removed in an upcoming release.
bool always_print_primitive_fields = false;
// Whether to always print fields which do not support presence if they would
// otherwise be omitted, namely:
// - Implicit presence fields set to their 0 value
// - Empty lists and maps
bool always_print_without_presence_fields = false;
// Whether to always print enums as ints. By default they are rendered as
// strings.
bool always_print_enums_as_ints = false;
Expand Down
4 changes: 4 additions & 0 deletions src/google/protobuf/json/json.cc
Expand Up @@ -34,6 +34,8 @@ absl::Status BinaryToJsonStream(google::protobuf::util::TypeResolver* resolver,
opts.preserve_proto_field_names = options.preserve_proto_field_names;
opts.always_print_enums_as_ints = options.always_print_enums_as_ints;
opts.always_print_primitive_fields = options.always_print_primitive_fields;
opts.always_print_without_presence_fields =
options.always_print_without_presence_fields;
opts.unquote_int64_if_possible = options.unquote_int64_if_possible;

// TODO: Drop this setting.
Expand Down Expand Up @@ -88,6 +90,8 @@ absl::Status MessageToJsonString(const Message& message, std::string* output,
opts.preserve_proto_field_names = options.preserve_proto_field_names;
opts.always_print_enums_as_ints = options.always_print_enums_as_ints;
opts.always_print_primitive_fields = options.always_print_primitive_fields;
opts.always_print_without_presence_fields =
options.always_print_without_presence_fields;
opts.unquote_int64_if_possible = options.unquote_int64_if_possible;

// TODO: Drop this setting.
Expand Down
18 changes: 14 additions & 4 deletions src/google/protobuf/json/json.h
Expand Up @@ -39,11 +39,21 @@ struct PrintOptions {
// Whether to add spaces, line breaks and indentation to make the JSON output
// easy to read.
bool add_whitespace = false;
// Whether to always print primitive fields. By default proto3 primitive
// fields with default values will be omitted in JSON output. For example, an
// int32 field set to 0 will be omitted. Set this flag to true will override
// the default behavior and print primitive fields regardless of their values.
// Whether to always print the following types of fields even if they would
// otherwise be omitted:
// - Implicit presence fields set to their 0 value
// - Empty lists and maps
// - Proto2 optional and required scalar fields which are not present (but not
// Proto3 optional scalar fields).
// Note: This option is deprecated in favor of
// always_print_without_presence_fields which treats proto2 and proto3
// optionals the same and will be removed in an upcoming release.
bool always_print_primitive_fields = false;
// Whether to always print fields which do not support presence if they would
// otherwise be omitted, namely:
// - Implicit presence fields set to their 0 value
// - Empty lists and maps
bool always_print_without_presence_fields = false;
// Whether to always print enums as ints. By default they are rendered as
// strings.
bool always_print_enums_as_ints = false;
Expand Down
168 changes: 120 additions & 48 deletions src/google/protobuf/json/json_test.cc
Expand Up @@ -179,61 +179,60 @@ TEST_P(JsonTest, TestDefaultValues) {

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\":[]"
EXPECT_THAT(ToJson(m, options), IsOkAndHolds(R"({"boolValue":false,)"
R"("int32Value":0,)"
R"("int64Value":"0",)"
R"("uint32Value":0,)"
R"("uint64Value":"0",)"
R"("floatValue":0,)"
R"("doubleValue":0,)"
R"("stringValue":"",)"
R"("bytesValue":"",)"
R"("enumValue":"FOO",)"
R"("repeatedBoolValue":[],)"
R"("repeatedInt32Value":[],)"
R"("repeatedInt64Value":[],)"
R"("repeatedUint32Value":[],)"
R"("repeatedUint64Value":[],)"
R"("repeatedFloatValue":[],)"
R"("repeatedDoubleValue":[],)"
R"("repeatedStringValue":[],)"
R"("repeatedBytesValue":[],)"
R"("repeatedEnumValue":[],)"
R"("repeatedMessageValue":[])"
"}"));

m.set_string_value("i am a test string value");
m.set_bytes_value("i am a test bytes value");
m.set_optional_bool_value(false);
m.set_optional_string_value("");
m.set_optional_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\":[],"
"\"optionalBoolValue\":false,"
"\"optionalStringValue\":\"\","
"\"optionalBytesValue\":\"\""
"}"));
EXPECT_THAT(ToJson(m, options),
IsOkAndHolds(R"({"boolValue":false,)"
R"("int32Value":0,)"
R"("int64Value":"0",)"
R"("uint32Value":0,)"
R"("uint64Value":"0",)"
R"("floatValue":0,)"
R"("doubleValue":0,)"
R"("stringValue":"i am a test string value",)"
R"("bytesValue":"aSBhbSBhIHRlc3QgYnl0ZXMgdmFsdWU=",)"
R"("enumValue":"FOO",)"
R"("repeatedBoolValue":[],)"
R"("repeatedInt32Value":[],)"
R"("repeatedInt64Value":[],)"
R"("repeatedUint32Value":[],)"
R"("repeatedUint64Value":[],)"
R"("repeatedFloatValue":[],)"
R"("repeatedDoubleValue":[],)"
R"("repeatedStringValue":[],)"
R"("repeatedBytesValue":[],)"
R"("repeatedEnumValue":[],)"
R"("repeatedMessageValue":[],)"
R"("optionalBoolValue":false,)"
R"("optionalStringValue":"",)"
R"("optionalBytesValue":"")"
"}"));

EXPECT_THAT(
ToJson(protobuf_unittest::TestAllTypes(), options),
Expand Down Expand Up @@ -274,6 +273,79 @@ TEST_P(JsonTest, TestDefaultValues) {
R"(,"cordWithZero":"12\u00003","replacementString":"${unknown}"})"));
}

TEST_P(JsonTest, TestAlwaysPrintWithoutPresenceFields) {
TestMessage m;
EXPECT_THAT(ToJson(m), IsOkAndHolds("{}"));

PrintOptions options;
options.always_print_without_presence_fields = true;
EXPECT_THAT(ToJson(m, options), IsOkAndHolds(R"({"boolValue":false,)"
R"("int32Value":0,)"
R"("int64Value":"0",)"
R"("uint32Value":0,)"
R"("uint64Value":"0",)"
R"("floatValue":0,)"
R"("doubleValue":0,)"
R"("stringValue":"",)"
R"("bytesValue":"",)"
R"("enumValue":"FOO",)"
R"("repeatedBoolValue":[],)"
R"("repeatedInt32Value":[],)"
R"("repeatedInt64Value":[],)"
R"("repeatedUint32Value":[],)"
R"("repeatedUint64Value":[],)"
R"("repeatedFloatValue":[],)"
R"("repeatedDoubleValue":[],)"
R"("repeatedStringValue":[],)"
R"("repeatedBytesValue":[],)"
R"("repeatedEnumValue":[],)"
R"("repeatedMessageValue":[])"
"}"));

m.set_string_value("i am a test string value");
m.set_bytes_value("i am a test bytes value");
m.set_optional_bool_value(false);
m.set_optional_string_value("");
m.set_optional_bytes_value("");
EXPECT_THAT(ToJson(m, options),
IsOkAndHolds(R"({"boolValue":false,)"
R"("int32Value":0,)"
R"("int64Value":"0",)"
R"("uint32Value":0,)"
R"("uint64Value":"0",)"
R"("floatValue":0,)"
R"("doubleValue":0,)"
R"("stringValue":"i am a test string value",)"
R"("bytesValue":"aSBhbSBhIHRlc3QgYnl0ZXMgdmFsdWU=",)"
R"("enumValue":"FOO",)"
R"("repeatedBoolValue":[],)"
R"("repeatedInt32Value":[],)"
R"("repeatedInt64Value":[],)"
R"("repeatedUint32Value":[],)"
R"("repeatedUint64Value":[],)"
R"("repeatedFloatValue":[],)"
R"("repeatedDoubleValue":[],)"
R"("repeatedStringValue":[],)"
R"("repeatedBytesValue":[],)"
R"("repeatedEnumValue":[],)"
R"("repeatedMessageValue":[],)"
R"("optionalBoolValue":false,)"
R"("optionalStringValue":"",)"
R"("optionalBytesValue":"")"
"}"));

EXPECT_THAT(
ToJson(protobuf_unittest::TestAllTypes(), options),
IsOkAndHolds(
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":[]})"));
}

TEST_P(JsonTest, TestPreserveProtoFieldNames) {
TestMessage m;
m.mutable_message_value();
Expand Down

0 comments on commit 671b61b

Please sign in to comment.