Add a new 'includingDefaultValueWithoutPresenceFields' option to the Java parser which is intended to replace the current 'includingDefaultValueFields'.

The old flag accidentally had inconsistent behavior between proto2 optional and proto3 optional fields, the new flag treats them consistently (and is consistent with the preexisting behavior of the Go JSON serializer).

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

PiperOrigin-RevId: 603449195
pull/15730/head
Protobuf Team Bot 10 months ago committed by Sandy Zhang
parent 1f3bf1d31e
commit 043191b84d
  1. 205
      java/util/src/main/java/com/google/protobuf/util/JsonFormat.java
  2. 229
      java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java
  3. 6
      java/util/src/test/proto/com/google/protobuf/util/json_test.proto
  4. 82
      java/util/src/test/proto/com/google/protobuf/util/json_test_proto2.proto

@ -8,6 +8,7 @@
package com.google.protobuf.util;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.BaseEncoding;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gson.Gson;
@ -29,7 +30,6 @@ import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.Descriptors.OneofDescriptor;
import com.google.protobuf.DoubleValue;
import com.google.protobuf.Duration;
import com.google.protobuf.DynamicMessage;
@ -86,30 +86,30 @@ public class JsonFormat {
return new Printer(
com.google.protobuf.TypeRegistry.getEmptyTypeRegistry(),
TypeRegistry.getEmptyTypeRegistry(),
/* alwaysOutputDefaultValueFields */ false,
/* includingDefaultValueFields */ Collections.<FieldDescriptor>emptySet(),
ShouldPrintDefaults.ONLY_IF_PRESENT,
/* includingDefaultValueFields */ ImmutableSet.of(),
/* preservingProtoFieldNames */ false,
/* omittingInsignificantWhitespace */ false,
/* printingEnumsAsInts */ false,
/* sortingMapKeys */ false);
}
/**
* A Printer converts a protobuf message to the proto3 JSON format.
*/
private enum ShouldPrintDefaults {
ONLY_IF_PRESENT, // The "normal" behavior; the others add more compared to this baseline.
ALWAYS_PRINT_EXCEPT_MESSAGES_AND_ONEOFS,
ALWAYS_PRINT_WITHOUT_PRESENCE_FIELDS,
ALWAYS_PRINT_SPECIFIED_FIELDS
}
/** A Printer converts a protobuf message to the proto3 JSON format. */
public static class Printer {
private final com.google.protobuf.TypeRegistry registry;
private final TypeRegistry oldRegistry;
// NOTE: There are 3 states for these *defaultValueFields variables:
// 1) Default - alwaysOutput is false & including is empty set. Fields only output if they are
// set to non-default values.
// 2) No-args includingDefaultValueFields() called - alwaysOutput is true & including is
// irrelevant (but set to empty set). All fields are output regardless of their values.
// 3) includingDefaultValueFields(Set<FieldDescriptor>) called - alwaysOutput is false &
// including is set to the specified set. Fields in that set are always output & fields not
// in that set are only output if set to non-default values.
private boolean alwaysOutputDefaultValueFields;
private Set<FieldDescriptor> includingDefaultValueFields;
private final ShouldPrintDefaults shouldPrintDefaults;
// Empty unless shouldPrintDefaults is set to ALWAYS_PRINT_SPECIFIED_FIELDS.
private final Set<FieldDescriptor> includingDefaultValueFields;
private final boolean preservingProtoFieldNames;
private final boolean omittingInsignificantWhitespace;
private final boolean printingEnumsAsInts;
@ -118,7 +118,7 @@ public class JsonFormat {
private Printer(
com.google.protobuf.TypeRegistry registry,
TypeRegistry oldRegistry,
boolean alwaysOutputDefaultValueFields,
ShouldPrintDefaults shouldOutputDefaults,
Set<FieldDescriptor> includingDefaultValueFields,
boolean preservingProtoFieldNames,
boolean omittingInsignificantWhitespace,
@ -126,7 +126,7 @@ public class JsonFormat {
boolean sortingMapKeys) {
this.registry = registry;
this.oldRegistry = oldRegistry;
this.alwaysOutputDefaultValueFields = alwaysOutputDefaultValueFields;
this.shouldPrintDefaults = shouldOutputDefaults;
this.includingDefaultValueFields = includingDefaultValueFields;
this.preservingProtoFieldNames = preservingProtoFieldNames;
this.omittingInsignificantWhitespace = omittingInsignificantWhitespace;
@ -148,7 +148,7 @@ public class JsonFormat {
return new Printer(
com.google.protobuf.TypeRegistry.getEmptyTypeRegistry(),
oldRegistry,
alwaysOutputDefaultValueFields,
shouldPrintDefaults,
includingDefaultValueFields,
preservingProtoFieldNames,
omittingInsignificantWhitespace,
@ -170,7 +170,7 @@ public class JsonFormat {
return new Printer(
registry,
oldRegistry,
alwaysOutputDefaultValueFields,
shouldPrintDefaults,
includingDefaultValueFields,
preservingProtoFieldNames,
omittingInsignificantWhitespace,
@ -179,18 +179,27 @@ public class JsonFormat {
}
/**
* Creates a new {@link Printer} that will also print fields set to their
* defaults. Empty repeated fields and map fields will be printed as well.
* The new Printer clones all other configurations from the current
* {@link Printer}.
* Creates a new {@link Printer} that will always print fields unless they are a message type or
* in a oneof.
*
* <p>Note that this does print Proto2 Optional but does not print Proto3 Optional fields, as
* the latter is represented using a synthetic oneof.
*
* <p>The new Printer clones all other configurations from the current {@link Printer}.
*
* @deprecated Prefer {@link #includingDefaultValueWithoutPresenceFields}
*/
@Deprecated
public Printer includingDefaultValueFields() {
checkUnsetIncludingDefaultValueFields();
if (shouldPrintDefaults != ShouldPrintDefaults.ONLY_IF_PRESENT) {
throw new IllegalStateException(
"JsonFormat includingDefaultValueFields has already been set.");
}
return new Printer(
registry,
oldRegistry,
true,
Collections.<FieldDescriptor>emptySet(),
ShouldPrintDefaults.ALWAYS_PRINT_EXCEPT_MESSAGES_AND_ONEOFS,
ImmutableSet.of(),
preservingProtoFieldNames,
omittingInsignificantWhitespace,
printingEnumsAsInts,
@ -198,56 +207,76 @@ public class JsonFormat {
}
/**
* Creates a new {@link Printer} that prints enum field values as integers instead of as
* string. The new Printer clones all other configurations from the current {@link Printer}.
* Creates a new {@link Printer} that will also print default-valued fields if their
* FieldDescriptors are found in the supplied set. Empty repeated fields and map fields will be
* printed as well, if they match. The new Printer clones all other configurations from the
* current {@link Printer}. Call includingDefaultValueFields() with no args to unconditionally
* output all fields.
*
* <p>Note that non-repeated message fields or fields in a oneof are not honored if provided
* here.
*/
public Printer printingEnumsAsInts() {
checkUnsetPrintingEnumsAsInts();
public Printer includingDefaultValueFields(Set<FieldDescriptor> fieldsToAlwaysOutput) {
Preconditions.checkArgument(
null != fieldsToAlwaysOutput && !fieldsToAlwaysOutput.isEmpty(),
"Non-empty Set must be supplied for includingDefaultValueFields.");
if (shouldPrintDefaults != ShouldPrintDefaults.ONLY_IF_PRESENT) {
throw new IllegalStateException(
"JsonFormat includingDefaultValueFields has already been set.");
}
return new Printer(
registry,
oldRegistry,
alwaysOutputDefaultValueFields,
includingDefaultValueFields,
ShouldPrintDefaults.ALWAYS_PRINT_SPECIFIED_FIELDS,
ImmutableSet.copyOf(fieldsToAlwaysOutput),
preservingProtoFieldNames,
omittingInsignificantWhitespace,
true,
printingEnumsAsInts,
sortingMapKeys);
}
private void checkUnsetPrintingEnumsAsInts() {
if (printingEnumsAsInts) {
throw new IllegalStateException("JsonFormat printingEnumsAsInts has already been set.");
/**
* Creates a new {@link Printer} that will print any field that does not support presence even
* if it would not otherwise be printed (empty repeated fields, empty map fields, and implicit
* presence scalars set to their default value). The new Printer clones all other configurations
* from the current {@link Printer}.
*/
public Printer includingDefaultValueWithoutPresenceFields() {
if (shouldPrintDefaults != ShouldPrintDefaults.ONLY_IF_PRESENT) {
throw new IllegalStateException(
"JsonFormat includingDefaultValueFields has already been set.");
}
return new Printer(
registry,
oldRegistry,
ShouldPrintDefaults.ALWAYS_PRINT_WITHOUT_PRESENCE_FIELDS,
ImmutableSet.of(),
preservingProtoFieldNames,
omittingInsignificantWhitespace,
printingEnumsAsInts,
sortingMapKeys);
}
/**
* Creates a new {@link Printer} that will also print default-valued fields if their
* FieldDescriptors are found in the supplied set. Empty repeated fields and map fields will be
* printed as well, if they match. The new Printer clones all other configurations from the
* current {@link Printer}. Call includingDefaultValueFields() with no args to unconditionally
* output all fields.
* Creates a new {@link Printer} that prints enum field values as integers instead of as string.
* The new Printer clones all other configurations from the current {@link Printer}.
*/
public Printer includingDefaultValueFields(Set<FieldDescriptor> fieldsToAlwaysOutput) {
Preconditions.checkArgument(
null != fieldsToAlwaysOutput && !fieldsToAlwaysOutput.isEmpty(),
"Non-empty Set must be supplied for includingDefaultValueFields.");
checkUnsetIncludingDefaultValueFields();
public Printer printingEnumsAsInts() {
checkUnsetPrintingEnumsAsInts();
return new Printer(
registry,
oldRegistry,
false,
Collections.unmodifiableSet(new HashSet<>(fieldsToAlwaysOutput)),
shouldPrintDefaults,
includingDefaultValueFields,
preservingProtoFieldNames,
omittingInsignificantWhitespace,
printingEnumsAsInts,
true,
sortingMapKeys);
}
private void checkUnsetIncludingDefaultValueFields() {
if (alwaysOutputDefaultValueFields || !includingDefaultValueFields.isEmpty()) {
throw new IllegalStateException(
"JsonFormat includingDefaultValueFields has already been set.");
private void checkUnsetPrintingEnumsAsInts() {
if (printingEnumsAsInts) {
throw new IllegalStateException("JsonFormat printingEnumsAsInts has already been set.");
}
}
@ -261,7 +290,7 @@ public class JsonFormat {
return new Printer(
registry,
oldRegistry,
alwaysOutputDefaultValueFields,
shouldPrintDefaults,
includingDefaultValueFields,
true,
omittingInsignificantWhitespace,
@ -290,7 +319,7 @@ public class JsonFormat {
return new Printer(
registry,
oldRegistry,
alwaysOutputDefaultValueFields,
shouldPrintDefaults,
includingDefaultValueFields,
preservingProtoFieldNames,
true,
@ -313,7 +342,7 @@ public class JsonFormat {
return new Printer(
registry,
oldRegistry,
alwaysOutputDefaultValueFields,
shouldPrintDefaults,
includingDefaultValueFields,
preservingProtoFieldNames,
omittingInsignificantWhitespace,
@ -334,7 +363,7 @@ public class JsonFormat {
new PrinterImpl(
registry,
oldRegistry,
alwaysOutputDefaultValueFields,
shouldPrintDefaults,
includingDefaultValueFields,
preservingProtoFieldNames,
output,
@ -685,7 +714,7 @@ public class JsonFormat {
private static final class PrinterImpl {
private final com.google.protobuf.TypeRegistry registry;
private final TypeRegistry oldRegistry;
private final boolean alwaysOutputDefaultValueFields;
private final ShouldPrintDefaults shouldPrintDefaults;
private final Set<FieldDescriptor> includingDefaultValueFields;
private final boolean preservingProtoFieldNames;
private final boolean printingEnumsAsInts;
@ -703,7 +732,7 @@ public class JsonFormat {
PrinterImpl(
com.google.protobuf.TypeRegistry registry,
TypeRegistry oldRegistry,
boolean alwaysOutputDefaultValueFields,
ShouldPrintDefaults shouldPrintDefaults,
Set<FieldDescriptor> includingDefaultValueFields,
boolean preservingProtoFieldNames,
Appendable jsonOutput,
@ -712,7 +741,7 @@ public class JsonFormat {
boolean sortingMapKeys) {
this.registry = registry;
this.oldRegistry = oldRegistry;
this.alwaysOutputDefaultValueFields = alwaysOutputDefaultValueFields;
this.shouldPrintDefaults = shouldPrintDefaults;
this.includingDefaultValueFields = includingDefaultValueFields;
this.preservingProtoFieldNames = preservingProtoFieldNames;
this.printingEnumsAsInts = printingEnumsAsInts;
@ -965,6 +994,28 @@ public class JsonFormat {
printRepeatedFieldValue(field, message.getField(field));
}
// Whether a set option means the corresponding field should be printed even if it normally
// wouldn't be.
private boolean shouldSpeciallyPrint(FieldDescriptor field) {
switch (shouldPrintDefaults) {
case ONLY_IF_PRESENT:
return false;
case ALWAYS_PRINT_EXCEPT_MESSAGES_AND_ONEOFS:
return !field.hasPresence()
|| (field.getJavaType() != FieldDescriptor.JavaType.MESSAGE
&& field.getContainingOneof() == null);
case ALWAYS_PRINT_WITHOUT_PRESENCE_FIELDS:
return !field.hasPresence();
case ALWAYS_PRINT_SPECIFIED_FIELDS:
// For legacy code compatibility, we don't honor non-repeated message or oneof fields even
// if they're explicitly requested. :(
return !(field.getJavaType() == FieldDescriptor.JavaType.MESSAGE && !field.isRepeated())
&& field.getContainingOneof() == null
&& includingDefaultValueFields.contains(field);
}
throw new AssertionError("Unknown shouldPrintDefaults: " + shouldPrintDefaults);
}
/** Prints a regular message with an optional type URL. */
private void print(MessageOrBuilder message, @Nullable String typeUrl) throws IOException {
generator.print("{" + blankOrNewLine);
@ -975,31 +1026,23 @@ public class JsonFormat {
generator.print("\"@type\":" + blankOrSpace + gson.toJson(typeUrl));
printedField = true;
}
Map<FieldDescriptor, Object> fieldsToPrint = null;
if (alwaysOutputDefaultValueFields || !includingDefaultValueFields.isEmpty()) {
// message.getAllFields() will already contain all of the fields that would be
// printed normally (non-empty repeated fields, with-presence fields that are set, implicit
// presence fields that have a nonzero value). Loop over all of the fields to add any more
// fields that should be printed based on the shouldPrintDefaults setting.
Map<FieldDescriptor, Object> fieldsToPrint;
if (shouldPrintDefaults == ShouldPrintDefaults.ONLY_IF_PRESENT) {
fieldsToPrint = message.getAllFields();
} else {
fieldsToPrint = new TreeMap<FieldDescriptor, Object>(message.getAllFields());
for (FieldDescriptor field : message.getDescriptorForType().getFields()) {
if (field.isOptional()) {
if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE
&& !message.hasField(field)) {
// Always skip empty optional message fields. If not we will recurse indefinitely if
// a message has itself as a sub-field.
continue;
}
OneofDescriptor oneof = field.getContainingOneof();
if (oneof != null && !message.hasField(field)) {
// Skip all oneof fields except the one that is actually set
continue;
}
}
if (!fieldsToPrint.containsKey(field)
&& (alwaysOutputDefaultValueFields || includingDefaultValueFields.contains(field))) {
if (shouldSpeciallyPrint(field)) {
fieldsToPrint.put(field, message.getField(field));
}
}
} else {
fieldsToPrint = message.getAllFields();
}
for (Map.Entry<FieldDescriptor, Object> field : fieldsToPrint.entrySet()) {
if (printedField) {
// Add line-endings for the previous field.

@ -46,6 +46,7 @@ import com.google.protobuf.util.proto.JsonTestProto.TestRecursive;
import com.google.protobuf.util.proto.JsonTestProto.TestStruct;
import com.google.protobuf.util.proto.JsonTestProto.TestTimestamp;
import com.google.protobuf.util.proto.JsonTestProto.TestWrappers;
import com.google.protobuf.util.proto.JsonTestProto2.TestAllTypesProto2;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -1483,48 +1484,60 @@ public class JsonFormatTest {
}
@Test
public void testIncludingDefaultValueFields() throws Exception {
public void testDefaultValueOptionsProto3() throws Exception {
TestAllTypes message = TestAllTypes.getDefaultInstance();
assertThat(JsonFormat.printer().print(message)).isEqualTo("{\n}");
String expectedJsonWithDefaults =
"{\n"
+ " \"optionalInt32\": 0,\n"
+ " \"optionalInt64\": \"0\",\n"
+ " \"optionalUint32\": 0,\n"
+ " \"optionalUint64\": \"0\",\n"
+ " \"optionalSint32\": 0,\n"
+ " \"optionalSint64\": \"0\",\n"
+ " \"optionalFixed32\": 0,\n"
+ " \"optionalFixed64\": \"0\",\n"
+ " \"optionalSfixed32\": 0,\n"
+ " \"optionalSfixed64\": \"0\",\n"
+ " \"optionalFloat\": 0.0,\n"
+ " \"optionalDouble\": 0.0,\n"
+ " \"optionalBool\": false,\n"
+ " \"optionalString\": \"\",\n"
+ " \"optionalBytes\": \"\",\n"
+ " \"optionalNestedEnum\": \"FOO\",\n"
+ " \"repeatedInt32\": [],\n"
+ " \"repeatedInt64\": [],\n"
+ " \"repeatedUint32\": [],\n"
+ " \"repeatedUint64\": [],\n"
+ " \"repeatedSint32\": [],\n"
+ " \"repeatedSint64\": [],\n"
+ " \"repeatedFixed32\": [],\n"
+ " \"repeatedFixed64\": [],\n"
+ " \"repeatedSfixed32\": [],\n"
+ " \"repeatedSfixed64\": [],\n"
+ " \"repeatedFloat\": [],\n"
+ " \"repeatedDouble\": [],\n"
+ " \"repeatedBool\": [],\n"
+ " \"repeatedString\": [],\n"
+ " \"repeatedBytes\": [],\n"
+ " \"repeatedNestedMessage\": [],\n"
+ " \"repeatedNestedEnum\": [],\n"
+ " \"optionalAliasedEnum\": \"ALIAS_FOO\",\n"
+ " \"repeatedRecursive\": []\n"
+ "}";
// includingDefaultValueFields() and includingDefaultValueWithoutPresenceFields() should
// behave identically on the proto3 test message:
assertThat(JsonFormat.printer().includingDefaultValueFields().print(message))
.isEqualTo(
"{\n"
+ " \"optionalInt32\": 0,\n"
+ " \"optionalInt64\": \"0\",\n"
+ " \"optionalUint32\": 0,\n"
+ " \"optionalUint64\": \"0\",\n"
+ " \"optionalSint32\": 0,\n"
+ " \"optionalSint64\": \"0\",\n"
+ " \"optionalFixed32\": 0,\n"
+ " \"optionalFixed64\": \"0\",\n"
+ " \"optionalSfixed32\": 0,\n"
+ " \"optionalSfixed64\": \"0\",\n"
+ " \"optionalFloat\": 0.0,\n"
+ " \"optionalDouble\": 0.0,\n"
+ " \"optionalBool\": false,\n"
+ " \"optionalString\": \"\",\n"
+ " \"optionalBytes\": \"\",\n"
+ " \"optionalNestedEnum\": \"FOO\",\n"
+ " \"repeatedInt32\": [],\n"
+ " \"repeatedInt64\": [],\n"
+ " \"repeatedUint32\": [],\n"
+ " \"repeatedUint64\": [],\n"
+ " \"repeatedSint32\": [],\n"
+ " \"repeatedSint64\": [],\n"
+ " \"repeatedFixed32\": [],\n"
+ " \"repeatedFixed64\": [],\n"
+ " \"repeatedSfixed32\": [],\n"
+ " \"repeatedSfixed64\": [],\n"
+ " \"repeatedFloat\": [],\n"
+ " \"repeatedDouble\": [],\n"
+ " \"repeatedBool\": [],\n"
+ " \"repeatedString\": [],\n"
+ " \"repeatedBytes\": [],\n"
+ " \"repeatedNestedMessage\": [],\n"
+ " \"repeatedNestedEnum\": [],\n"
+ " \"optionalAliasedEnum\": \"ALIAS_FOO\"\n"
+ "}");
.isEqualTo(expectedJsonWithDefaults);
assertThat(JsonFormat.printer().includingDefaultValueWithoutPresenceFields().print(message))
.isEqualTo(expectedJsonWithDefaults);
}
@Test
public void testDefaultValueForSpecificFieldsOptionProto3() throws Exception {
TestAllTypes message = TestAllTypes.getDefaultInstance();
Set<FieldDescriptor> fixedFields = new HashSet<>();
for (FieldDescriptor fieldDesc : TestAllTypes.getDescriptor().getFields()) {
if (fieldDesc.getName().contains("_fixed")) {
@ -1553,7 +1566,28 @@ public class JsonFormatTest {
+ " \"repeatedFixed32\": [],\n"
+ " \"repeatedFixed64\": []\n"
+ "}");
}
@Test
public void testDefaultValueForSpecificFieldsProto3_doesntHonorMessageFields() throws Exception {
TestAllTypes message = TestAllTypes.getDefaultInstance();
Set<FieldDescriptor> fixedFields =
ImmutableSet.of(
TestAllTypes.getDescriptor().findFieldByName("optional_bool"),
TestAllTypes.getDescriptor().findFieldByName("optional_recursive"));
assertThat(JsonFormat.printer().includingDefaultValueFields(fixedFields).print(message))
.isEqualTo("{\n \"optionalBool\": false\n}");
}
@Test
public void testRejectChangingDefaultFieldOptionMultipleTimes() throws Exception {
Set<FieldDescriptor> fixedFields = new HashSet<>();
for (FieldDescriptor fieldDesc : TestAllTypes.getDescriptor().getFields()) {
if (fieldDesc.getName().contains("_fixed")) {
fixedFields.add(fieldDesc);
}
}
try {
JsonFormat.printer().includingDefaultValueFields().includingDefaultValueFields();
assertWithMessage("IllegalStateException is expected.").fail();
@ -1634,7 +1668,10 @@ public class JsonFormatTest {
.that(e.getMessage().contains("includingDefaultValueFields"))
.isTrue();
}
}
@Test
public void testDefaultValuesOptionProto3Maps() throws Exception {
TestMap mapMessage = TestMap.getDefaultInstance();
assertThat(JsonFormat.printer().print(mapMessage)).isEqualTo("{\n}");
assertThat(JsonFormat.printer().includingDefaultValueFields().print(mapMessage))
@ -1697,16 +1734,25 @@ public class JsonFormatTest {
+ " \"int32ToEnumMap\": {\n"
+ " }\n"
+ "}");
}
@Test
public void testDefaultValueOptionsProto3Oneofs() throws Exception {
TestOneof oneofMessage = TestOneof.getDefaultInstance();
assertThat(JsonFormat.printer().print(oneofMessage)).isEqualTo("{\n}");
assertThat(JsonFormat.printer().includingDefaultValueFields().print(oneofMessage))
.isEqualTo("{\n}");
assertThat(
JsonFormat.printer().includingDefaultValueWithoutPresenceFields().print(oneofMessage))
.isEqualTo("{\n}");
oneofMessage = TestOneof.newBuilder().setOneofInt32(42).build();
assertThat(JsonFormat.printer().print(oneofMessage)).isEqualTo("{\n \"oneofInt32\": 42\n}");
assertThat(JsonFormat.printer().includingDefaultValueFields().print(oneofMessage))
.isEqualTo("{\n \"oneofInt32\": 42\n}");
assertThat(
JsonFormat.printer().includingDefaultValueWithoutPresenceFields().print(oneofMessage))
.isEqualTo("{\n \"oneofInt32\": 42\n}");
TestOneof.Builder oneofBuilder = TestOneof.newBuilder();
mergeFromJson("{\n" + " \"oneofNullValue\": null \n" + "}", oneofBuilder);
@ -1715,6 +1761,113 @@ public class JsonFormatTest {
.isEqualTo("{\n \"oneofNullValue\": null\n}");
assertThat(JsonFormat.printer().includingDefaultValueFields().print(oneofMessage))
.isEqualTo("{\n \"oneofNullValue\": null\n}");
assertThat(
JsonFormat.printer().includingDefaultValueWithoutPresenceFields().print(oneofMessage))
.isEqualTo("{\n \"oneofNullValue\": null\n}");
}
@Test
public void testIncludingDefaultValueOptionsWithProto2Optional() throws Exception {
TestAllTypesProto2 message = TestAllTypesProto2.getDefaultInstance();
assertThat(JsonFormat.printer().print(message)).isEqualTo("{\n}");
// includingDefaultValueFields() and includingDefaultValueWithoutPresenceFields()
// behave differently on a proto2 message: the former includes the proto2 explicit presence
// fields and the latter does not.
assertThat(JsonFormat.printer().includingDefaultValueFields().print(message))
.isEqualTo(
"{\n"
+ " \"optionalInt32\": 0,\n"
+ " \"optionalInt64\": \"0\",\n"
+ " \"optionalUint32\": 0,\n"
+ " \"optionalUint64\": \"0\",\n"
+ " \"optionalSint32\": 0,\n"
+ " \"optionalSint64\": \"0\",\n"
+ " \"optionalFixed32\": 0,\n"
+ " \"optionalFixed64\": \"0\",\n"
+ " \"optionalSfixed32\": 0,\n"
+ " \"optionalSfixed64\": \"0\",\n"
+ " \"optionalFloat\": 0.0,\n"
+ " \"optionalDouble\": 0.0,\n"
+ " \"optionalBool\": false,\n"
+ " \"optionalString\": \"\",\n"
+ " \"optionalBytes\": \"\",\n"
+ " \"optionalNestedEnum\": \"FOO\",\n"
+ " \"repeatedInt32\": [],\n"
+ " \"repeatedInt64\": [],\n"
+ " \"repeatedUint32\": [],\n"
+ " \"repeatedUint64\": [],\n"
+ " \"repeatedSint32\": [],\n"
+ " \"repeatedSint64\": [],\n"
+ " \"repeatedFixed32\": [],\n"
+ " \"repeatedFixed64\": [],\n"
+ " \"repeatedSfixed32\": [],\n"
+ " \"repeatedSfixed64\": [],\n"
+ " \"repeatedFloat\": [],\n"
+ " \"repeatedDouble\": [],\n"
+ " \"repeatedBool\": [],\n"
+ " \"repeatedString\": [],\n"
+ " \"repeatedBytes\": [],\n"
+ " \"repeatedNestedMessage\": [],\n"
+ " \"repeatedNestedEnum\": [],\n"
+ " \"optionalAliasedEnum\": \"ALIAS_FOO\",\n"
+ " \"repeatedRecursive\": []\n"
+ "}");
assertThat(JsonFormat.printer().includingDefaultValueWithoutPresenceFields().print(message))
.isEqualTo(
"{\n"
+ " \"repeatedInt32\": [],\n"
+ " \"repeatedInt64\": [],\n"
+ " \"repeatedUint32\": [],\n"
+ " \"repeatedUint64\": [],\n"
+ " \"repeatedSint32\": [],\n"
+ " \"repeatedSint64\": [],\n"
+ " \"repeatedFixed32\": [],\n"
+ " \"repeatedFixed64\": [],\n"
+ " \"repeatedSfixed32\": [],\n"
+ " \"repeatedSfixed64\": [],\n"
+ " \"repeatedFloat\": [],\n"
+ " \"repeatedDouble\": [],\n"
+ " \"repeatedBool\": [],\n"
+ " \"repeatedString\": [],\n"
+ " \"repeatedBytes\": [],\n"
+ " \"repeatedNestedMessage\": [],\n"
+ " \"repeatedNestedEnum\": [],\n"
+ " \"repeatedRecursive\": []\n"
+ "}");
}
@Test
public void testDefaultValueForSpecificFieldsOptionProto2() throws Exception {
TestAllTypesProto2 message = TestAllTypesProto2.getDefaultInstance();
Set<FieldDescriptor> fixedFields = new HashSet<>();
for (FieldDescriptor fieldDesc : TestAllTypesProto2.getDescriptor().getFields()) {
if (fieldDesc.getName().contains("_fixed")) {
fixedFields.add(fieldDesc);
}
}
assertThat(JsonFormat.printer().includingDefaultValueFields(fixedFields).print(message))
.isEqualTo(
"{\n"
+ " \"optionalFixed32\": 0,\n"
+ " \"optionalFixed64\": \"0\",\n"
+ " \"repeatedFixed32\": [],\n"
+ " \"repeatedFixed64\": []\n"
+ "}");
TestAllTypesProto2 messageNonDefaults =
message.toBuilder().setOptionalInt64(1234).setOptionalFixed32(3232).build();
assertThat(
JsonFormat.printer().includingDefaultValueFields(fixedFields).print(messageNonDefaults))
.isEqualTo(
"{\n"
+ " \"optionalInt64\": \"1234\",\n"
+ " \"optionalFixed32\": 3232,\n"
+ " \"optionalFixed64\": \"0\",\n"
+ " \"repeatedFixed32\": [],\n"
+ " \"repeatedFixed64\": []\n"
+ "}");
}
@Test

@ -5,6 +5,8 @@
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// LINT: LEGACY_NAMES
syntax = "proto3";
package json_test;
@ -58,6 +60,9 @@ message TestAllTypes {
NestedMessage optional_nested_message = 18;
NestedEnum optional_nested_enum = 21;
AliasedEnum optional_aliased_enum = 52;
TestRecursive optional_recursive = 53;
optional int32 explicit_presence_int32 = 54;
// Repeated
repeated int32 repeated_int32 = 31;
@ -77,6 +82,7 @@ message TestAllTypes {
repeated bytes repeated_bytes = 45;
repeated NestedMessage repeated_nested_message = 48;
repeated NestedEnum repeated_nested_enum = 51;
repeated TestRecursive repeated_recursive = 55;
}
message TestOneof {

@ -0,0 +1,82 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. 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
// LINT: LEGACY_NAMES
syntax = "proto2";
package json_test_proto2;
option java_package = "com.google.protobuf.util.proto";
option java_outer_classname = "JsonTestProto2";
message TestAllTypesProto2 {
enum NestedEnum {
FOO = 0;
BAR = 1;
BAZ = 2;
}
enum AliasedEnum {
option allow_alias = true;
ALIAS_FOO = 0;
ALIAS_BAR = 1;
ALIAS_BAZ = 2;
QUX = 2;
qux = 2;
bAz = 2;
}
message NestedMessage {
optional int32 value = 1;
}
optional int32 optional_int32 = 1;
optional int64 optional_int64 = 2;
optional uint32 optional_uint32 = 3;
optional uint64 optional_uint64 = 4;
optional sint32 optional_sint32 = 5;
optional sint64 optional_sint64 = 6;
optional fixed32 optional_fixed32 = 7;
optional fixed64 optional_fixed64 = 8;
optional sfixed32 optional_sfixed32 = 9;
optional sfixed64 optional_sfixed64 = 10;
optional float optional_float = 11;
optional double optional_double = 12;
optional bool optional_bool = 13;
optional string optional_string = 14;
optional bytes optional_bytes = 15;
optional NestedMessage optional_nested_message = 18;
optional NestedEnum optional_nested_enum = 21;
optional AliasedEnum optional_aliased_enum = 52;
optional TestRecursive optional_recursive = 53;
// Repeated
repeated int32 repeated_int32 = 31;
repeated int64 repeated_int64 = 32;
repeated uint32 repeated_uint32 = 33;
repeated uint64 repeated_uint64 = 34;
repeated sint32 repeated_sint32 = 35;
repeated sint64 repeated_sint64 = 36;
repeated fixed32 repeated_fixed32 = 37;
repeated fixed64 repeated_fixed64 = 38;
repeated sfixed32 repeated_sfixed32 = 39;
repeated sfixed64 repeated_sfixed64 = 40;
repeated float repeated_float = 41;
repeated double repeated_double = 42;
repeated bool repeated_bool = 43;
repeated string repeated_string = 44;
repeated bytes repeated_bytes = 45;
repeated NestedMessage repeated_nested_message = 48;
repeated NestedEnum repeated_nested_enum = 51;
repeated TestRecursive repeated_recursive = 55;
}
message TestRecursive {
optional int32 value = 1;
optional TestRecursive nested = 2;
}
Loading…
Cancel
Save