diff --git a/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java b/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java index cdcc8d9ccf..9e89d6eace 100644 --- a/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java +++ b/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java @@ -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.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) 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 includingDefaultValueFields; + private final ShouldPrintDefaults shouldPrintDefaults; + + // Empty unless shouldPrintDefaults is set to ALWAYS_PRINT_SPECIFIED_FIELDS. + private final Set 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 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. + * + *

Note that this does print Proto2 Optional but does not print Proto3 Optional fields, as + * the latter is represented using a synthetic oneof. + * + *

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.emptySet(), + ShouldPrintDefaults.ALWAYS_PRINT_EXCEPT_MESSAGES_AND_ONEOFS, + ImmutableSet.of(), preservingProtoFieldNames, omittingInsignificantWhitespace, printingEnumsAsInts, @@ -198,56 +207,73 @@ 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. */ - public Printer printingEnumsAsInts() { - checkUnsetPrintingEnumsAsInts(); + public Printer includingDefaultValueFields(Set 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 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 +287,7 @@ public class JsonFormat { return new Printer( registry, oldRegistry, - alwaysOutputDefaultValueFields, + shouldPrintDefaults, includingDefaultValueFields, true, omittingInsignificantWhitespace, @@ -290,7 +316,7 @@ public class JsonFormat { return new Printer( registry, oldRegistry, - alwaysOutputDefaultValueFields, + shouldPrintDefaults, includingDefaultValueFields, preservingProtoFieldNames, true, @@ -313,7 +339,7 @@ public class JsonFormat { return new Printer( registry, oldRegistry, - alwaysOutputDefaultValueFields, + shouldPrintDefaults, includingDefaultValueFields, preservingProtoFieldNames, omittingInsignificantWhitespace, @@ -334,7 +360,7 @@ public class JsonFormat { new PrinterImpl( registry, oldRegistry, - alwaysOutputDefaultValueFields, + shouldPrintDefaults, includingDefaultValueFields, preservingProtoFieldNames, output, @@ -685,7 +711,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 includingDefaultValueFields; private final boolean preservingProtoFieldNames; private final boolean printingEnumsAsInts; @@ -703,7 +729,7 @@ public class JsonFormat { PrinterImpl( com.google.protobuf.TypeRegistry registry, TypeRegistry oldRegistry, - boolean alwaysOutputDefaultValueFields, + ShouldPrintDefaults shouldPrintDefaults, Set includingDefaultValueFields, boolean preservingProtoFieldNames, Appendable jsonOutput, @@ -712,7 +738,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 +991,24 @@ 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: + return 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 +1019,23 @@ public class JsonFormat { generator.print("\"@type\":" + blankOrSpace + gson.toJson(typeUrl)); printedField = true; } - Map 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 fieldsToPrint; + if (shouldPrintDefaults == ShouldPrintDefaults.ONLY_IF_PRESENT) { + fieldsToPrint = message.getAllFields(); + } else { fieldsToPrint = new TreeMap(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 field : fieldsToPrint.entrySet()) { if (printedField) { // Add line-endings for the previous field. diff --git a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java index 6d75edb611..36c31f49a0 100644 --- a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java +++ b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java @@ -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 fixedFields = new HashSet<>(); for (FieldDescriptor fieldDesc : TestAllTypes.getDescriptor().getFields()) { if (fieldDesc.getName().contains("_fixed")) { @@ -1553,7 +1566,16 @@ public class JsonFormatTest { + " \"repeatedFixed32\": [],\n" + " \"repeatedFixed64\": []\n" + "}"); + } + @Test + public void testRejectChangingDefaultFieldOptionMultipleTimes() throws Exception { + Set 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 +1656,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 +1722,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 +1749,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 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 diff --git a/java/util/src/test/proto/com/google/protobuf/util/json_test.proto b/java/util/src/test/proto/com/google/protobuf/util/json_test.proto index 34c951005b..28831198fb 100644 --- a/java/util/src/test/proto/com/google/protobuf/util/json_test.proto +++ b/java/util/src/test/proto/com/google/protobuf/util/json_test.proto @@ -58,6 +58,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 +80,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 { diff --git a/java/util/src/test/proto/com/google/protobuf/util/json_test_proto2.proto b/java/util/src/test/proto/com/google/protobuf/util/json_test_proto2.proto new file mode 100644 index 0000000000..5cec400057 --- /dev/null +++ b/java/util/src/test/proto/com/google/protobuf/util/json_test_proto2.proto @@ -0,0 +1,80 @@ +// 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 + +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; +}