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..5092588132 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,28 @@ 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 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. + * + *

Note that non-repeated message fields or fields in a oneof are not honored if provided + * here. */ - public Printer includingDefaultValueFields() { - checkUnsetIncludingDefaultValueFields(); + 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, - true, - Collections.emptySet(), + ShouldPrintDefaults.ALWAYS_PRINT_SPECIFIED_FIELDS, + ImmutableSet.copyOf(fieldsToAlwaysOutput), preservingProtoFieldNames, omittingInsignificantWhitespace, printingEnumsAsInts, @@ -198,56 +208,46 @@ 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 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 printingEnumsAsInts() { - checkUnsetPrintingEnumsAsInts(); + public Printer alwaysPrintFieldsWithNoPresence() { + if (shouldPrintDefaults != ShouldPrintDefaults.ONLY_IF_PRESENT) { + throw new IllegalStateException("Only one of the JsonFormat defaults options can be set."); + } return new Printer( registry, oldRegistry, - alwaysOutputDefaultValueFields, - includingDefaultValueFields, + ShouldPrintDefaults.ALWAYS_PRINT_WITHOUT_PRESENCE_FIELDS, + ImmutableSet.of(), 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 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 +261,7 @@ public class JsonFormat { return new Printer( registry, oldRegistry, - alwaysOutputDefaultValueFields, + shouldPrintDefaults, includingDefaultValueFields, true, omittingInsignificantWhitespace, @@ -290,7 +290,7 @@ public class JsonFormat { return new Printer( registry, oldRegistry, - alwaysOutputDefaultValueFields, + shouldPrintDefaults, includingDefaultValueFields, preservingProtoFieldNames, true, @@ -313,7 +313,7 @@ public class JsonFormat { return new Printer( registry, oldRegistry, - alwaysOutputDefaultValueFields, + shouldPrintDefaults, includingDefaultValueFields, preservingProtoFieldNames, omittingInsignificantWhitespace, @@ -334,7 +334,7 @@ public class JsonFormat { new PrinterImpl( registry, oldRegistry, - alwaysOutputDefaultValueFields, + shouldPrintDefaults, includingDefaultValueFields, preservingProtoFieldNames, output, @@ -685,7 +685,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 +703,7 @@ public class JsonFormat { PrinterImpl( com.google.protobuf.TypeRegistry registry, TypeRegistry oldRegistry, - boolean alwaysOutputDefaultValueFields, + ShouldPrintDefaults shouldPrintDefaults, Set includingDefaultValueFields, boolean preservingProtoFieldNames, Appendable jsonOutput, @@ -712,7 +712,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 +965,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 +997,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..ae345b4aa5 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,50 +1484,59 @@ 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}"); - 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" - + "}"); + + 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" + + "}"; + + assertThat(JsonFormat.printer().alwaysPrintFieldsWithNoPresence().print(message)) + .isEqualTo(expectedJsonWithDefaults); + } + + @Test + public void testDefaultValueForSpecificFieldsOptionProto2() throws Exception { + TestAllTypesProto2 message = TestAllTypesProto2.getDefaultInstance(); Set fixedFields = new HashSet<>(); - for (FieldDescriptor fieldDesc : TestAllTypes.getDescriptor().getFields()) { + for (FieldDescriptor fieldDesc : TestAllTypesProto2.getDescriptor().getFields()) { if (fieldDesc.getName().contains("_fixed")) { fixedFields.add(fieldDesc); } @@ -1541,7 +1551,7 @@ public class JsonFormatTest { + " \"repeatedFixed64\": []\n" + "}"); - TestAllTypes messageNonDefaults = + TestAllTypesProto2 messageNonDefaults = message.toBuilder().setOptionalInt64(1234).setOptionalFixed32(3232).build(); assertThat( JsonFormat.printer().includingDefaultValueFields(fixedFields).print(messageNonDefaults)) @@ -1553,168 +1563,6 @@ public class JsonFormatTest { + " \"repeatedFixed32\": [],\n" + " \"repeatedFixed64\": []\n" + "}"); - - try { - JsonFormat.printer().includingDefaultValueFields().includingDefaultValueFields(); - assertWithMessage("IllegalStateException is expected.").fail(); - } catch (IllegalStateException e) { - // Expected. - assertWithMessage("Exception message should mention includingDefaultValueFields.") - .that(e.getMessage().contains("includingDefaultValueFields")) - .isTrue(); - } - - try { - JsonFormat.printer().includingDefaultValueFields().includingDefaultValueFields(fixedFields); - assertWithMessage("IllegalStateException is expected.").fail(); - } catch (IllegalStateException e) { - // Expected. - assertWithMessage("Exception message should mention includingDefaultValueFields.") - .that(e.getMessage().contains("includingDefaultValueFields")) - .isTrue(); - } - - try { - JsonFormat.printer().includingDefaultValueFields(fixedFields).includingDefaultValueFields(); - assertWithMessage("IllegalStateException is expected.").fail(); - } catch (IllegalStateException e) { - // Expected. - assertWithMessage("Exception message should mention includingDefaultValueFields.") - .that(e.getMessage().contains("includingDefaultValueFields")) - .isTrue(); - } - - try { - JsonFormat.printer() - .includingDefaultValueFields(fixedFields) - .includingDefaultValueFields(fixedFields); - assertWithMessage("IllegalStateException is expected.").fail(); - } catch (IllegalStateException e) { - // Expected. - assertWithMessage("Exception message should mention includingDefaultValueFields.") - .that(e.getMessage().contains("includingDefaultValueFields")) - .isTrue(); - } - - Set intFields = new HashSet<>(); - for (FieldDescriptor fieldDesc : TestAllTypes.getDescriptor().getFields()) { - if (fieldDesc.getName().contains("_int")) { - intFields.add(fieldDesc); - } - } - - try { - JsonFormat.printer() - .includingDefaultValueFields(intFields) - .includingDefaultValueFields(fixedFields); - assertWithMessage("IllegalStateException is expected.").fail(); - } catch (IllegalStateException e) { - // Expected. - assertWithMessage("Exception message should mention includingDefaultValueFields.") - .that(e.getMessage().contains("includingDefaultValueFields")) - .isTrue(); - } - - try { - JsonFormat.printer().includingDefaultValueFields(null); - assertWithMessage("IllegalArgumentException is expected.").fail(); - } catch (IllegalArgumentException e) { - // Expected. - assertWithMessage("Exception message should mention includingDefaultValueFields.") - .that(e.getMessage().contains("includingDefaultValueFields")) - .isTrue(); - } - - try { - JsonFormat.printer().includingDefaultValueFields(Collections.emptySet()); - assertWithMessage("IllegalArgumentException is expected.").fail(); - } catch (IllegalArgumentException e) { - // Expected. - assertWithMessage("Exception message should mention includingDefaultValueFields.") - .that(e.getMessage().contains("includingDefaultValueFields")) - .isTrue(); - } - - TestMap mapMessage = TestMap.getDefaultInstance(); - assertThat(JsonFormat.printer().print(mapMessage)).isEqualTo("{\n}"); - assertThat(JsonFormat.printer().includingDefaultValueFields().print(mapMessage)) - .isEqualTo( - "{\n" - + " \"int32ToInt32Map\": {\n" - + " },\n" - + " \"int64ToInt32Map\": {\n" - + " },\n" - + " \"uint32ToInt32Map\": {\n" - + " },\n" - + " \"uint64ToInt32Map\": {\n" - + " },\n" - + " \"sint32ToInt32Map\": {\n" - + " },\n" - + " \"sint64ToInt32Map\": {\n" - + " },\n" - + " \"fixed32ToInt32Map\": {\n" - + " },\n" - + " \"fixed64ToInt32Map\": {\n" - + " },\n" - + " \"sfixed32ToInt32Map\": {\n" - + " },\n" - + " \"sfixed64ToInt32Map\": {\n" - + " },\n" - + " \"boolToInt32Map\": {\n" - + " },\n" - + " \"stringToInt32Map\": {\n" - + " },\n" - + " \"int32ToInt64Map\": {\n" - + " },\n" - + " \"int32ToUint32Map\": {\n" - + " },\n" - + " \"int32ToUint64Map\": {\n" - + " },\n" - + " \"int32ToSint32Map\": {\n" - + " },\n" - + " \"int32ToSint64Map\": {\n" - + " },\n" - + " \"int32ToFixed32Map\": {\n" - + " },\n" - + " \"int32ToFixed64Map\": {\n" - + " },\n" - + " \"int32ToSfixed32Map\": {\n" - + " },\n" - + " \"int32ToSfixed64Map\": {\n" - + " },\n" - + " \"int32ToFloatMap\": {\n" - + " },\n" - + " \"int32ToDoubleMap\": {\n" - + " },\n" - + " \"int32ToBoolMap\": {\n" - + " },\n" - + " \"int32ToStringMap\": {\n" - + " },\n" - + " \"int32ToBytesMap\": {\n" - + " },\n" - + " \"int32ToMessageMap\": {\n" - + " },\n" - + " \"int32ToEnumMap\": {\n" - + " }\n" - + "}"); - - TestOneof oneofMessage = TestOneof.getDefaultInstance(); - assertThat(JsonFormat.printer().print(oneofMessage)).isEqualTo("{\n}"); - assertThat(JsonFormat.printer().includingDefaultValueFields().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}"); - - TestOneof.Builder oneofBuilder = TestOneof.newBuilder(); - mergeFromJson("{\n" + " \"oneofNullValue\": null \n" + "}", oneofBuilder); - oneofMessage = oneofBuilder.build(); - assertThat(JsonFormat.printer().print(oneofMessage)) - .isEqualTo("{\n \"oneofNullValue\": null\n}"); - assertThat(JsonFormat.printer().includingDefaultValueFields().print(oneofMessage)) - .isEqualTo("{\n \"oneofNullValue\": null\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..3cde445ee2 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 @@ -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 { 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..b1cb80f7ca --- /dev/null +++ b/java/util/src/test/proto/com/google/protobuf/util/json_test_proto2.proto @@ -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; +} diff --git a/python/google/protobuf/internal/json_format_test.py b/python/google/protobuf/internal/json_format_test.py index 6c4d2dac0b..d1a0777d15 100644 --- a/python/google/protobuf/internal/json_format_test.py +++ b/python/google/protobuf/internal/json_format_test.py @@ -17,6 +17,7 @@ import unittest from google.protobuf import descriptor_pool from google.protobuf import json_format from google.protobuf.internal import more_messages_pb2 +from google.protobuf.internal import test_proto2_pb2 from google.protobuf.internal import test_proto3_optional_pb2 from google.protobuf import any_pb2 @@ -57,7 +58,7 @@ class JsonFormatBase(unittest.TestCase): message.repeated_uint64_value.append(9007199254740991) message.repeated_float_value.append(0) - message.repeated_double_value.append(1E-15) + message.repeated_double_value.append(1e-15) message.repeated_double_value.append(float('inf')) message.repeated_bool_value.append(True) message.repeated_bool_value.append(False) @@ -72,64 +73,66 @@ class JsonFormatBase(unittest.TestCase): self.message = message def CheckParseBack(self, message, parsed_message): - json_format.Parse(json_format.MessageToJson(message), - parsed_message) + json_format.Parse(json_format.MessageToJson(message), parsed_message) self.assertEqual(message, parsed_message) def CheckError(self, text, error_message): message = json_format_proto3_pb2.TestMessage() - self.assertRaisesRegex(json_format.ParseError, error_message, - json_format.Parse, text, message) + self.assertRaisesRegex( + json_format.ParseError, error_message, json_format.Parse, text, message + ) class JsonFormatTest(JsonFormatBase): def testEmptyMessageToJson(self): message = json_format_proto3_pb2.TestMessage() - self.assertEqual(json_format.MessageToJson(message), - '{}') + self.assertEqual(json_format.MessageToJson(message), '{}') parsed_message = json_format_proto3_pb2.TestMessage() self.CheckParseBack(message, parsed_message) def testPartialMessageToJson(self): message = json_format_proto3_pb2.TestMessage( - string_value='test', - repeated_int32_value=[89, 4]) - self.assertEqual(json.loads(json_format.MessageToJson(message)), - json.loads('{"stringValue": "test", ' - '"repeatedInt32Value": [89, 4]}')) + string_value='test', repeated_int32_value=[89, 4] + ) + self.assertEqual( + json.loads(json_format.MessageToJson(message)), + json.loads('{"stringValue": "test", "repeatedInt32Value": [89, 4]}'), + ) parsed_message = json_format_proto3_pb2.TestMessage() self.CheckParseBack(message, parsed_message) def testAllFieldsToJson(self): message = json_format_proto3_pb2.TestMessage() - text = ('{"int32Value": 20, ' - '"int64Value": "-20", ' - '"uint32Value": 3120987654,' - '"uint64Value": "12345678900",' - '"floatValue": "-Infinity",' - '"doubleValue": 3.1415,' - '"boolValue": true,' - '"stringValue": "foo",' - '"bytesValue": "YmFy",' - '"messageValue": {"value": 10},' - '"enumValue": "BAR",' - '"repeatedInt32Value": [2147483647, -2147483648],' - '"repeatedInt64Value": ["9007199254740992", "-9007199254740992"],' - '"repeatedUint32Value": [268435455, 134217727],' - '"repeatedUint64Value": ["9007199254740992", "9007199254740991"],' - '"repeatedFloatValue": [0],' - '"repeatedDoubleValue": [1e-15, "Infinity"],' - '"repeatedBoolValue": [true, false],' - '"repeatedStringValue": ["Few symbols!#$,;", "bar"],' - '"repeatedBytesValue": ["Zm9v", "YmFy"],' - '"repeatedMessageValue": [{"value": 10}, {"value": 11}],' - '"repeatedEnumValue": ["FOO", "BAR"]' - '}') + text = ( + '{"int32Value": 20, ' + '"int64Value": "-20", ' + '"uint32Value": 3120987654,' + '"uint64Value": "12345678900",' + '"floatValue": "-Infinity",' + '"doubleValue": 3.1415,' + '"boolValue": true,' + '"stringValue": "foo",' + '"bytesValue": "YmFy",' + '"messageValue": {"value": 10},' + '"enumValue": "BAR",' + '"repeatedInt32Value": [2147483647, -2147483648],' + '"repeatedInt64Value": ["9007199254740992", "-9007199254740992"],' + '"repeatedUint32Value": [268435455, 134217727],' + '"repeatedUint64Value": ["9007199254740992", "9007199254740991"],' + '"repeatedFloatValue": [0],' + '"repeatedDoubleValue": [1e-15, "Infinity"],' + '"repeatedBoolValue": [true, false],' + '"repeatedStringValue": ["Few symbols!#$,;", "bar"],' + '"repeatedBytesValue": ["Zm9v", "YmFy"],' + '"repeatedMessageValue": [{"value": 10}, {"value": 11}],' + '"repeatedEnumValue": ["FOO", "BAR"]' + '}' + ) self.FillAllFields(message) self.assertEqual( - json.loads(json_format.MessageToJson(message)), - json.loads(text)) + json.loads(json_format.MessageToJson(message)), json.loads(text) + ) parsed_message = json_format_proto3_pb2.TestMessage() json_format.Parse(text, parsed_message) self.assertEqual(message, parsed_message) @@ -138,8 +141,7 @@ class JsonFormatTest(JsonFormatBase): text = '{\n "enumValue": 999\n}' message = json_format_proto3_pb2.TestMessage() message.enum_value = 999 - self.assertEqual(json_format.MessageToJson(message), - text) + self.assertEqual(json_format.MessageToJson(message), text) parsed_message = json_format_proto3_pb2.TestMessage() json_format.Parse(text, parsed_message) self.assertEqual(message, parsed_message) @@ -150,16 +152,16 @@ class JsonFormatTest(JsonFormatBase): ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension message.message_set.Extensions[ext1].i = 23 message.message_set.Extensions[ext2].str = 'foo' - message_text = json_format.MessageToJson( - message - ) + message_text = json_format.MessageToJson(message) parsed_message = unittest_mset_pb2.TestMessageSetContainer() json_format.Parse(message_text, parsed_message) self.assertEqual(message, parsed_message) def testExtensionErrors(self): - self.CheckError('{"[extensionField]": {}}', - 'Message type proto3.TestMessage does not have extensions') + self.CheckError( + '{"[extensionField]": {}}', + 'Message type proto3.TestMessage does not have extensions', + ) def testExtensionToDictAndBack(self): message = unittest_mset_pb2.TestMessageSetContainer() @@ -167,9 +169,7 @@ class JsonFormatTest(JsonFormatBase): ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension message.message_set.Extensions[ext1].i = 23 message.message_set.Extensions[ext2].str = 'foo' - message_dict = json_format.MessageToDict( - message - ) + message_dict = json_format.MessageToDict(message) parsed_message = unittest_mset_pb2.TestMessageSetContainer() json_format.ParseDict(message_dict, parsed_message) self.assertEqual(message, parsed_message) @@ -178,9 +178,7 @@ class JsonFormatTest(JsonFormatBase): message = unittest_pb2.TestAllExtensions() ext1 = unittest_pb2.TestNestedExtension.test message.Extensions[ext1] = 'data' - message_dict = json_format.MessageToDict( - message - ) + message_dict = json_format.MessageToDict(message) parsed_message = unittest_pb2.TestAllExtensions() json_format.ParseDict(message_dict, parsed_message) self.assertEqual(message, parsed_message) @@ -188,7 +186,7 @@ class JsonFormatTest(JsonFormatBase): def testJsonParseDictToAnyDoesNotAlterInput(self): orig_dict = { 'int32Value': 20, - '@type': 'type.googleapis.com/proto3.TestMessage' + '@type': 'type.googleapis.com/proto3.TestMessage', } copied_dict = json.loads(json.dumps(orig_dict)) parsed_message = any_pb2.Any() @@ -196,25 +194,20 @@ class JsonFormatTest(JsonFormatBase): self.assertEqual(copied_dict, orig_dict) def testExtensionSerializationDictMatchesProto3Spec(self): - """See go/proto3-json-spec for spec. - """ + """See go/proto3-json-spec for spec.""" message = unittest_mset_pb2.TestMessageSetContainer() ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension message.message_set.Extensions[ext1].i = 23 message.message_set.Extensions[ext2].str = 'foo' - message_dict = json_format.MessageToDict( - message - ) + message_dict = json_format.MessageToDict(message) golden_dict = { 'messageSet': { - '[protobuf_unittest.' - 'TestMessageSetExtension1.message_set_extension]': { + '[protobuf_unittest.TestMessageSetExtension1.message_set_extension]': { 'i': 23, }, - '[protobuf_unittest.' - 'TestMessageSetExtension2.message_set_extension]': { - 'str': u'foo', + '[protobuf_unittest.TestMessageSetExtension2.message_set_extension]': { + 'str': 'foo', }, }, } @@ -224,103 +217,100 @@ class JsonFormatTest(JsonFormatBase): self.assertEqual(message, parsed_msg) def testExtensionSerializationDictMatchesProto3SpecMore(self): - """See go/proto3-json-spec for spec. - """ + """See go/proto3-json-spec for spec.""" message = json_format_pb2.TestMessageWithExtension() ext = json_format_pb2.TestExtension.ext message.Extensions[ext].value = 'stuff' - message_dict = json_format.MessageToDict( - message - ) + message_dict = json_format.MessageToDict(message) expected_dict = { '[protobuf_unittest.TestExtension.ext]': { - 'value': u'stuff', + 'value': 'stuff', }, } self.assertEqual(expected_dict, message_dict) def testExtensionSerializationJsonMatchesProto3Spec(self): - """See go/proto3-json-spec for spec. - """ + """See go/proto3-json-spec for spec.""" message = unittest_mset_pb2.TestMessageSetContainer() ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension message.message_set.Extensions[ext1].i = 23 message.message_set.Extensions[ext2].str = 'foo' - message_text = json_format.MessageToJson( - message - ) - ext1_text = ('protobuf_unittest.TestMessageSetExtension1.' - 'message_set_extension') - ext2_text = ('protobuf_unittest.TestMessageSetExtension2.' - 'message_set_extension') - golden_text = ('{"messageSet": {' - ' "[%s]": {' - ' "i": 23' - ' },' - ' "[%s]": {' - ' "str": "foo"' - ' }' - '}}') % (ext1_text, ext2_text) + message_text = json_format.MessageToJson(message) + ext1_text = 'protobuf_unittest.TestMessageSetExtension1.message_set_extension' + ext2_text = 'protobuf_unittest.TestMessageSetExtension2.message_set_extension' + golden_text = ( + '{"messageSet": {' + ' "[%s]": {' + ' "i": 23' + ' },' + ' "[%s]": {' + ' "str": "foo"' + ' }' + '}}' + ) % (ext1_text, ext2_text) self.assertEqual(json.loads(golden_text), json.loads(message_text)) def testJsonEscapeString(self): message = json_format_proto3_pb2.TestMessage() - message.string_value = '&\n<\"\r>\b\t\f\\\001/' + message.string_value = '&\n<"\r>\b\t\f\\\001/' message.string_value += (b'\xe2\x80\xa8\xe2\x80\xa9').decode('utf-8') self.assertEqual( json_format.MessageToJson(message), '{\n "stringValue": ' - '"&\\n<\\\"\\r>\\b\\t\\f\\\\\\u0001/\\u2028\\u2029"\n}') + '"&\\n<\\"\\r>\\b\\t\\f\\\\\\u0001/\\u2028\\u2029"\n}', + ) parsed_message = json_format_proto3_pb2.TestMessage() self.CheckParseBack(message, parsed_message) - text = u'{"int32Value": "\u0031"}' + text = '{"int32Value": "\u0031"}' json_format.Parse(text, message) self.assertEqual(message.int32_value, 1) - def testAlwaysSeriliaze(self): - message = json_format_proto3_pb2.TestMessage( - string_value='foo') + def testProto3Optional_IncludingDefaultValueWithoutPresenceFields(self): + message = test_proto3_optional_pb2.TestProto3Optional() self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads('{' - '"repeatedStringValue": [],' - '"stringValue": "foo",' - '"repeatedBoolValue": [],' - '"repeatedUint32Value": [],' - '"repeatedInt32Value": [],' - '"enumValue": "FOO",' - '"int32Value": 0,' - '"floatValue": 0,' - '"int64Value": "0",' - '"uint32Value": 0,' - '"repeatedBytesValue": [],' - '"repeatedUint64Value": [],' - '"repeatedDoubleValue": [],' - '"bytesValue": "",' - '"boolValue": false,' - '"repeatedEnumValue": [],' - '"uint64Value": "0",' - '"doubleValue": 0,' - '"repeatedFloatValue": [],' - '"repeatedInt64Value": [],' - '"repeatedMessageValue": []}')) - parsed_message = json_format_proto3_pb2.TestMessage() - self.CheckParseBack(message, parsed_message) + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ) + ), + json.loads('{"repeatedInt32": [], "repeatedNestedMessage": []}'), + ) + message.optional_int32 = 0 + self.assertEqual( + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ) + ), + json.loads( + '{"optionalInt32": 0,"repeatedInt32": [],' + ' "repeatedNestedMessage": []}' + ), + ) - def testProto3Optional(self): - message = test_proto3_optional_pb2.TestProto3Optional() + def testProto2_IncludingDefaultValueWithoutPresenceFields(self): + message = test_proto2_pb2.TestProto2() self.assertEqual( json.loads( json_format.MessageToJson( - message, including_default_value_fields=True)), - json.loads('{}')) + message, always_print_fields_with_no_presence=True + ) + ), + json.loads('{"repeatedInt32": [], "repeatedNestedMessage": []}'), + ) message.optional_int32 = 0 self.assertEqual( json.loads( json_format.MessageToJson( - message, including_default_value_fields=True)), - json.loads('{"optionalInt32": 0}')) + message, always_print_fields_with_no_presence=True + ) + ), + json.loads( + '{"optionalInt32": 0,"repeatedInt32": [],' + ' "repeatedNestedMessage": []}' + ), + ) def testIntegersRepresentedAsFloat(self): message = json_format_proto3_pb2.TestMessage() @@ -334,16 +324,23 @@ class JsonFormatTest(JsonFormatBase): def testMapFields(self): message = json_format_proto3_pb2.TestNestedMap() self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads('{' - '"boolMap": {},' - '"int32Map": {},' - '"int64Map": {},' - '"uint32Map": {},' - '"uint64Map": {},' - '"stringMap": {},' - '"mapMap": {}' - '}')) + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ) + ), + json.loads( + '{' + '"boolMap": {},' + '"int32Map": {},' + '"int64Map": {},' + '"uint32Map": {},' + '"uint64Map": {},' + '"stringMap": {},' + '"mapMap": {}' + '}' + ), + ) message.bool_map[True] = 1 message.bool_map[False] = 2 message.int32_map[1] = 2 @@ -358,16 +355,23 @@ class JsonFormatTest(JsonFormatBase): message.string_map['null'] = 3 message.map_map['1'].bool_map[True] = 3 self.assertEqual( - json.loads(json_format.MessageToJson(message, False)), - json.loads('{' - '"boolMap": {"false": 2, "true": 1},' - '"int32Map": {"1": 2, "2": 3},' - '"int64Map": {"1": 2, "2": 3},' - '"uint32Map": {"1": 2, "2": 3},' - '"uint64Map": {"1": 2, "2": 3},' - '"stringMap": {"1": 2, "null": 3},' - '"mapMap": {"1": {"boolMap": {"true": 3}}}' - '}')) + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=False + ) + ), + json.loads( + '{' + '"boolMap": {"false": 2, "true": 1},' + '"int32Map": {"1": 2, "2": 3},' + '"int64Map": {"1": 2, "2": 3},' + '"uint32Map": {"1": 2, "2": 3},' + '"uint64Map": {"1": 2, "2": 3},' + '"stringMap": {"1": 2, "null": 3},' + '"mapMap": {"1": {"boolMap": {"true": 3}}}' + '}' + ), + ) parsed_message = json_format_proto3_pb2.TestNestedMap() self.CheckParseBack(message, parsed_message) @@ -375,14 +379,18 @@ class JsonFormatTest(JsonFormatBase): message = json_format_proto3_pb2.TestOneof() # Always print does not affect oneof fields. self.assertEqual( - json_format.MessageToJson(message, True), - '{}') + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ), + '{}', + ) message.oneof_int32_value = 0 self.assertEqual( - json_format.MessageToJson(message, True), - '{\n' - ' "oneofInt32Value": 0\n' - '}') + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ), + '{\n "oneofInt32Value": 0\n}', + ) parsed_message = json_format_proto3_pb2.TestOneof() self.CheckParseBack(message, parsed_message) @@ -390,18 +398,21 @@ class JsonFormatTest(JsonFormatBase): # Test correct surrogate handling. message = json_format_proto3_pb2.TestMessage() json_format.Parse('{"stringValue": "\\uD83D\\uDE01"}', message) - self.assertEqual(message.string_value, - b'\xF0\x9F\x98\x81'.decode('utf-8', 'strict')) + self.assertEqual( + message.string_value, b'\xF0\x9F\x98\x81'.decode('utf-8', 'strict') + ) # Error case: unpaired high surrogate. self.CheckError( '{"stringValue": "\\uD83D"}', - r'Invalid \\uXXXX escape|Unpaired.*surrogate') + r'Invalid \\uXXXX escape|Unpaired.*surrogate', + ) # Unpaired low surrogate. self.CheckError( '{"stringValue": "\\uDE01"}', - r'Invalid \\uXXXX escape|Unpaired.*surrogate') + r'Invalid \\uXXXX escape|Unpaired.*surrogate', + ) def testTimestampMessage(self): message = json_format_proto3_pb2.TestTimestamp() @@ -420,23 +431,32 @@ class JsonFormatTest(JsonFormatBase): message.repeated_value.add().seconds = -62135596800 message.repeated_value[4].nanos = 0 self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads('{' - '"value": "1970-01-01T00:00:00Z",' - '"repeatedValue": [' - ' "1970-01-01T00:00:20.000000001Z",' - ' "1970-01-01T00:00:00.000010Z",' - ' "1973-03-03T09:46:40Z",' - ' "9999-12-31T23:59:59.999999999Z",' - ' "0001-01-01T00:00:00Z"' - ']' - '}')) + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ) + ), + json.loads( + '{' + '"value": "1970-01-01T00:00:00Z",' + '"repeatedValue": [' + ' "1970-01-01T00:00:20.000000001Z",' + ' "1970-01-01T00:00:00.000010Z",' + ' "1973-03-03T09:46:40Z",' + ' "9999-12-31T23:59:59.999999999Z",' + ' "0001-01-01T00:00:00Z"' + ']' + '}' + ), + ) parsed_message = json_format_proto3_pb2.TestTimestamp() self.CheckParseBack(message, parsed_message) - text = (r'{"value": "1970-01-01T00:00:00.01+08:00",' - r'"repeatedValue":[' - r' "1970-01-01T00:00:00.01+08:30",' - r' "1970-01-01T00:00:00.01-01:23"]}') + text = ( + r'{"value": "1970-01-01T00:00:00.01+08:00",' + r'"repeatedValue":[' + r' "1970-01-01T00:00:00.01+08:30",' + r' "1970-01-01T00:00:00.01-01:23"]}' + ) json_format.Parse(text, parsed_message) self.assertEqual(parsed_message.value.seconds, -8 * 3600) self.assertEqual(parsed_message.value.nanos, 10000000) @@ -455,17 +475,24 @@ class JsonFormatTest(JsonFormatBase): message.repeated_value.add().seconds = -315576000000 message.repeated_value.add().seconds = 315576000000 self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads('{' - '"value": "1s",' - '"repeatedValue": [' - ' "0.000000010s",' - ' "-1.000001s",' - ' "10.011s",' - ' "-315576000000s",' - ' "315576000000s"' - ']' - '}')) + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ) + ), + json.loads( + '{' + '"value": "1s",' + '"repeatedValue": [' + ' "0.000000010s",' + ' "-1.000001s",' + ' "10.011s",' + ' "-315576000000s",' + ' "315576000000s"' + ']' + '}' + ), + ) parsed_message = json_format_proto3_pb2.TestDuration() self.CheckParseBack(message, parsed_message) @@ -474,19 +501,21 @@ class JsonFormatTest(JsonFormatBase): message.value.paths.append('foo.bar') message.value.paths.append('bar') self.assertEqual( - json_format.MessageToJson(message, True), - '{\n' - ' "value": "foo.bar,bar"\n' - '}') + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ), + '{\n "value": "foo.bar,bar"\n}', + ) parsed_message = json_format_proto3_pb2.TestFieldMask() self.CheckParseBack(message, parsed_message) message.value.Clear() self.assertEqual( - json_format.MessageToJson(message, True), - '{\n' - ' "value": ""\n' - '}') + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ), + '{\n "value": ""\n}', + ) self.CheckParseBack(message, parsed_message) def testWrapperMessage(self): @@ -499,22 +528,29 @@ class JsonFormatTest(JsonFormatBase): message.repeated_bool_value.add().value = False message.repeated_int32_value.add() self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads('{\n' - ' "int32Value": 0,' - ' "boolValue": false,' - ' "stringValue": "",' - ' "bytesValue": "",' - ' "repeatedBoolValue": [true, false],' - ' "repeatedInt32Value": [0],' - ' "repeatedUint32Value": [],' - ' "repeatedFloatValue": [],' - ' "repeatedDoubleValue": [],' - ' "repeatedBytesValue": [],' - ' "repeatedInt64Value": [],' - ' "repeatedUint64Value": [],' - ' "repeatedStringValue": []' - '}')) + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ) + ), + json.loads( + '{\n' + ' "int32Value": 0,' + ' "boolValue": false,' + ' "stringValue": "",' + ' "bytesValue": "",' + ' "repeatedBoolValue": [true, false],' + ' "repeatedInt32Value": [0],' + ' "repeatedUint32Value": [],' + ' "repeatedFloatValue": [],' + ' "repeatedDoubleValue": [],' + ' "repeatedBytesValue": [],' + ' "repeatedInt64Value": [],' + ' "repeatedUint64Value": [],' + ' "repeatedStringValue": []' + '}' + ), + ) parsed_message = json_format_proto3_pb2.TestWrapper() self.CheckParseBack(message, parsed_message) @@ -534,7 +570,11 @@ class JsonFormatTest(JsonFormatBase): message.repeated_value.add()['age'] = 11 message.repeated_value.add() self.assertEqual( - json.loads(json_format.MessageToJson(message, False)), + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=False + ) + ), json.loads( '{' ' "value": {' @@ -551,7 +591,9 @@ class JsonFormatTest(JsonFormatBase): ' "list": [6, "seven", true, false, null, {"subkey2": 9}]' ' },' ' "repeatedValue": [{"age": 11}, {}]' - '}')) + '}' + ), + ) parsed_message = json_format_proto3_pb2.TestStruct() self.CheckParseBack(message, parsed_message) # check for regression; this used to raise @@ -565,23 +607,29 @@ class JsonFormatTest(JsonFormatBase): message.repeated_value.add().bool_value = False message.repeated_value.add().null_value = 0 self.assertEqual( - json.loads(json_format.MessageToJson(message, False)), json.loads( - '{' - ' "value": "hello",' - ' "repeatedValue": [11.1, false, null]' - '}')) + json_format.MessageToJson( + message, always_print_fields_with_no_presence=False + ) + ), + json.loads( + '{ "value": "hello", "repeatedValue": [11.1, false, null]}' + ), + ) parsed_message = json_format_proto3_pb2.TestValue() self.CheckParseBack(message, parsed_message) # Can't parse back if the Value message is not set. message.repeated_value.add() self.assertEqual( - json.loads(json_format.MessageToJson(message, False)), json.loads( - '{' - ' "value": "hello",' - ' "repeatedValue": [11.1, false, null, null]' - '}')) + json_format.MessageToJson( + message, always_print_fields_with_no_presence=False + ) + ), + json.loads( + '{ "value": "hello", "repeatedValue": [11.1, false, null, null]}' + ), + ) message.Clear() json_format.Parse('{"value": null}', message) self.assertEqual(message.value.WhichOneof('kind'), 'null_value') @@ -594,14 +642,16 @@ class JsonFormatTest(JsonFormatBase): self.assertEqual( 'Failed to serialize value field: Fail to serialize Infinity for ' 'Value.number_value, which would parse as string_value.', - str(cm.exception)) + str(cm.exception), + ) message.value.number_value = math.nan with self.assertRaises(json_format.SerializeToJsonError) as cm: json_format.MessageToJson(message) self.assertEqual( 'Failed to serialize value field: Fail to serialize NaN for ' 'Value.number_value, which would parse as string_value.', - str(cm.exception)) + str(cm.exception), + ) def testListValueMessage(self): message = json_format_proto3_pb2.TestListValue() @@ -613,26 +663,33 @@ class JsonFormatTest(JsonFormatBase): message.repeated_value.add().values.add().number_value = 1 message.repeated_value.add() self.assertEqual( - json.loads(json_format.MessageToJson(message, False)), + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=False + ) + ), json.loads( '{"value": [11.1, null, true, "hello", {"name": "Jim"}]\n,' - '"repeatedValue": [[1], []]}')) + '"repeatedValue": [[1], []]}' + ), + ) parsed_message = json_format_proto3_pb2.TestListValue() self.CheckParseBack(message, parsed_message) def testNullValue(self): message = json_format_proto3_pb2.TestOneof() message.oneof_null_value = 0 - self.assertEqual(json_format.MessageToJson(message), - '{\n "oneofNullValue": null\n}') + self.assertEqual( + json_format.MessageToJson(message), '{\n "oneofNullValue": null\n}' + ) parsed_message = json_format_proto3_pb2.TestOneof() self.CheckParseBack(message, parsed_message) # Check old format is also accepted new_message = json_format_proto3_pb2.TestOneof() - json_format.Parse('{\n "oneofNullValue": "NULL_VALUE"\n}', - new_message) - self.assertEqual(json_format.MessageToJson(new_message), - '{\n "oneofNullValue": null\n}') + json_format.Parse('{\n "oneofNullValue": "NULL_VALUE"\n}', new_message) + self.assertEqual( + json_format.MessageToJson(new_message), '{\n "oneofNullValue": null\n}' + ) def testAnyMessage(self): message = json_format_proto3_pb2.TestAny() @@ -645,7 +702,11 @@ class JsonFormatTest(JsonFormatBase): message.repeated_value.add().Pack(value2) message.repeated_value.add() self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ) + ), json.loads( '{\n' ' "repeatedValue": [ {\n' @@ -660,7 +721,9 @@ class JsonFormatTest(JsonFormatBase): ' "@type": "type.googleapis.com/proto3.MessageType",\n' ' "value": 1234\n' ' }\n' - '}\n')) + '}\n' + ), + ) parsed_message = json_format_proto3_pb2.TestAny() self.CheckParseBack(message, parsed_message) # Must print @type first @@ -671,14 +734,18 @@ class JsonFormatTest(JsonFormatBase): uint32_value=20, uint64_value=20, double_value=3.14, - string_value='foo') + string_value='foo', + ) message.Clear() message.value.Pack(test_message) self.assertEqual( - json_format.MessageToJson(message, False)[0:68], + json_format.MessageToJson( + message, always_print_fields_with_no_presence=False + )[0:68], '{\n' ' "value": {\n' - ' "@type": "type.googleapis.com/proto3.TestMessage"') + ' "@type": "type.googleapis.com/proto3.TestMessage"', + ) def testAnyMessageDescriptorPoolMissingType(self): packed_message = unittest_pb2.OneString() @@ -687,10 +754,16 @@ class JsonFormatTest(JsonFormatBase): message.any_value.Pack(packed_message) empty_pool = descriptor_pool.DescriptorPool() with self.assertRaises(TypeError) as cm: - json_format.MessageToJson(message, True, descriptor_pool=empty_pool) + json_format.MessageToJson( + message, + always_print_fields_with_no_presence=True, + descriptor_pool=empty_pool, + ) self.assertEqual( 'Can not find message descriptor by type_url:' - ' type.googleapis.com/protobuf_unittest.OneString', str(cm.exception)) + ' type.googleapis.com/protobuf_unittest.OneString', + str(cm.exception), + ) def testWellKnownInAnyMessage(self): message = any_pb2.Any() @@ -698,36 +771,54 @@ class JsonFormatTest(JsonFormatBase): int32_value.value = 1234 message.Pack(int32_value) self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ) + ), json.loads( '{\n' - ' "@type": \"type.googleapis.com/google.protobuf.Int32Value\",\n' + ' "@type": "type.googleapis.com/google.protobuf.Int32Value",\n' ' "value": 1234\n' - '}\n')) + '}\n' + ), + ) parsed_message = any_pb2.Any() self.CheckParseBack(message, parsed_message) timestamp = timestamp_pb2.Timestamp() message.Pack(timestamp) self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ) + ), json.loads( '{\n' ' "@type": "type.googleapis.com/google.protobuf.Timestamp",\n' ' "value": "1970-01-01T00:00:00Z"\n' - '}\n')) + '}\n' + ), + ) self.CheckParseBack(message, parsed_message) duration = duration_pb2.Duration() duration.seconds = 1 message.Pack(duration) self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ) + ), json.loads( '{\n' ' "@type": "type.googleapis.com/google.protobuf.Duration",\n' ' "value": "1s"\n' - '}\n')) + '}\n' + ), + ) self.CheckParseBack(message, parsed_message) field_mask = field_mask_pb2.FieldMask() @@ -735,24 +826,36 @@ class JsonFormatTest(JsonFormatBase): field_mask.paths.append('bar') message.Pack(field_mask) self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ) + ), json.loads( '{\n' ' "@type": "type.googleapis.com/google.protobuf.FieldMask",\n' ' "value": "foo.bar,bar"\n' - '}\n')) + '}\n' + ), + ) self.CheckParseBack(message, parsed_message) struct_message = struct_pb2.Struct() struct_message['name'] = 'Jim' message.Pack(struct_message) self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ) + ), json.loads( '{\n' ' "@type": "type.googleapis.com/google.protobuf.Struct",\n' ' "value": {"name": "Jim"}\n' - '}\n')) + '}\n' + ), + ) self.CheckParseBack(message, parsed_message) nested_any = any_pb2.Any() @@ -760,7 +863,11 @@ class JsonFormatTest(JsonFormatBase): nested_any.Pack(int32_value) message.Pack(nested_any) self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), + json.loads( + json_format.MessageToJson( + message, always_print_fields_with_no_presence=True + ) + ), json.loads( '{\n' ' "@type": "type.googleapis.com/google.protobuf.Any",\n' @@ -768,37 +875,41 @@ class JsonFormatTest(JsonFormatBase): ' "@type": "type.googleapis.com/google.protobuf.Int32Value",\n' ' "value": 5678\n' ' }\n' - '}\n')) + '}\n' + ), + ) self.CheckParseBack(message, parsed_message) def testParseNull(self): message = json_format_proto3_pb2.TestMessage() parsed_message = json_format_proto3_pb2.TestMessage() self.FillAllFields(parsed_message) - json_format.Parse('{"int32Value": null, ' - '"int64Value": null, ' - '"uint32Value": null,' - '"uint64Value": null,' - '"floatValue": null,' - '"doubleValue": null,' - '"boolValue": null,' - '"stringValue": null,' - '"bytesValue": null,' - '"messageValue": null,' - '"enumValue": null,' - '"repeatedInt32Value": null,' - '"repeatedInt64Value": null,' - '"repeatedUint32Value": null,' - '"repeatedUint64Value": null,' - '"repeatedFloatValue": null,' - '"repeatedDoubleValue": null,' - '"repeatedBoolValue": null,' - '"repeatedStringValue": null,' - '"repeatedBytesValue": null,' - '"repeatedMessageValue": null,' - '"repeatedEnumValue": null' - '}', - parsed_message) + json_format.Parse( + '{"int32Value": null, ' + '"int64Value": null, ' + '"uint32Value": null,' + '"uint64Value": null,' + '"floatValue": null,' + '"doubleValue": null,' + '"boolValue": null,' + '"stringValue": null,' + '"bytesValue": null,' + '"messageValue": null,' + '"enumValue": null,' + '"repeatedInt32Value": null,' + '"repeatedInt64Value": null,' + '"repeatedUint32Value": null,' + '"repeatedUint64Value": null,' + '"repeatedFloatValue": null,' + '"repeatedDoubleValue": null,' + '"repeatedBoolValue": null,' + '"repeatedStringValue": null,' + '"repeatedBytesValue": null,' + '"repeatedMessageValue": null,' + '"repeatedEnumValue": null' + '}', + parsed_message, + ) self.assertEqual(message, parsed_message) # Null and {} should have different behavior for sub message. self.assertFalse(parsed_message.HasField('message_value')) @@ -806,15 +917,20 @@ class JsonFormatTest(JsonFormatBase): self.assertTrue(parsed_message.HasField('message_value')) # Null is not allowed to be used as an element in repeated field. self.assertRaisesRegex( - json_format.ParseError, r'Failed to parse repeatedInt32Value field: ' + json_format.ParseError, + r'Failed to parse repeatedInt32Value field: ' r'null is not allowed to be used as an element in a repeated field ' - r'at TestMessage.repeatedInt32Value\[1\].', json_format.Parse, - '{"repeatedInt32Value":[1, null]}', parsed_message) + r'at TestMessage.repeatedInt32Value\[1\].', + json_format.Parse, + '{"repeatedInt32Value":[1, null]}', + parsed_message, + ) self.CheckError( '{"repeatedMessageValue":[null]}', r'Failed to parse repeatedMessageValue field: null is not' r' allowed to be used as an element in a repeated field ' - r'at TestMessage.repeatedMessageValue\[0\].') + r'at TestMessage.repeatedMessageValue\[0\].', + ) def testNanFloat(self): message = json_format_proto3_pb2.TestMessage() @@ -827,14 +943,16 @@ class JsonFormatTest(JsonFormatBase): def testParseDoubleToFloat(self): message = json_format_proto3_pb2.TestMessage() - text = ('{"repeatedDoubleValue": [3.4028235e+39, 1.4028235e-39]\n}') + text = '{"repeatedDoubleValue": [3.4028235e+39, 1.4028235e-39]\n}' json_format.Parse(text, message) - self.assertEqual(message.repeated_double_value[0], 3.4028235e+39) + self.assertEqual(message.repeated_double_value[0], 3.4028235e39) self.assertEqual(message.repeated_double_value[1], 1.4028235e-39) - text = ('{"repeatedFloatValue": [3.4028235e+39, 1.4028235e-39]\n}') + text = '{"repeatedFloatValue": [3.4028235e+39, 1.4028235e-39]\n}' self.CheckError( - text, r'Failed to parse repeatedFloatValue field: ' - r'Float value too large at TestMessage.repeatedFloatValue\[0\].') + text, + r'Failed to parse repeatedFloatValue field: ' + r'Float value too large at TestMessage.repeatedFloatValue\[0\].', + ) def testFloatPrecision(self): message = json_format_proto3_pb2.TestMessage() @@ -842,25 +960,24 @@ class JsonFormatTest(JsonFormatBase): # Set to 8 valid digits. text = '{\n "floatValue": 1.1234568\n}' self.assertEqual( - json_format.MessageToJson(message, float_precision=8), text) + json_format.MessageToJson(message, float_precision=8), text + ) # Set to 7 valid digits. text = '{\n "floatValue": 1.123457\n}' self.assertEqual( - json_format.MessageToJson(message, float_precision=7), text) + json_format.MessageToJson(message, float_precision=7), text + ) # Default float_precision will automatic print shortest float. message.float_value = 1.1000000011 text = '{\n "floatValue": 1.1\n}' - self.assertEqual( - json_format.MessageToJson(message), text) + self.assertEqual(json_format.MessageToJson(message), text) message.float_value = 1.00000075e-36 text = '{\n "floatValue": 1.00000075e-36\n}' - self.assertEqual( - json_format.MessageToJson(message), text) - message.float_value = 12345678912345e+11 + self.assertEqual(json_format.MessageToJson(message), text) + message.float_value = 12345678912345e11 text = '{\n "floatValue": 1.234568e+24\n}' - self.assertEqual( - json_format.MessageToJson(message), text) + self.assertEqual(json_format.MessageToJson(message), text) # Test a bunch of data and check json encode/decode do not # lose precision @@ -875,8 +992,7 @@ class JsonFormatTest(JsonFormatBase): self.CheckParseBack(message, msg2) def testParseEmptyText(self): - self.CheckError('', - r'Failed to load JSON: (Expecting value)|(No JSON).') + self.CheckError('', r'Failed to load JSON: (Expecting value)|(No JSON).') def testParseEnumValue(self): message = json_format_proto3_pb2.TestMessage() @@ -887,7 +1003,8 @@ class JsonFormatTest(JsonFormatBase): self.CheckError( '{"enumValue": "baz"}', 'Failed to parse enumValue field: Invalid enum value baz ' - 'for enum type proto3.EnumType at TestMessage.enumValue.') + 'for enum type proto3.EnumType at TestMessage.enumValue.', + ) # Proto3 accepts numeric unknown enums. text = '{"enumValue": 12345}' json_format.Parse(text, message) @@ -897,8 +1014,11 @@ class JsonFormatTest(JsonFormatBase): json_format.ParseError, 'Failed to parse optionalNestedEnum field: Invalid enum value 12345 ' 'for enum type protobuf_unittest.TestAllTypes.NestedEnum at ' - 'TestAllTypes.optionalNestedEnum.', json_format.Parse, - '{"optionalNestedEnum": 12345}', message) + 'TestAllTypes.optionalNestedEnum.', + json_format.Parse, + '{"optionalNestedEnum": 12345}', + message, + ) def testBytes(self): message = json_format_proto3_pb2.TestMessage() @@ -918,146 +1038,222 @@ class JsonFormatTest(JsonFormatBase): self.assertEqual(message.bytes_value, b'\x01\x02') def testParseBadIdentifer(self): - self.CheckError('{int32Value: 1}', - (r'Failed to load JSON: Expecting property name' - r'( enclosed in double quotes)?: line 1')) + self.CheckError( + '{int32Value: 1}', + ( + r'Failed to load JSON: Expecting property name' + r'( enclosed in double quotes)?: line 1' + ), + ) self.CheckError( '{"unknownName": 1}', 'Message type "proto3.TestMessage" has no field named ' - '"unknownName" at "TestMessage".') + '"unknownName" at "TestMessage".', + ) def testIgnoreUnknownField(self): text = '{"unknownName": 1}' parsed_message = json_format_proto3_pb2.TestMessage() json_format.Parse(text, parsed_message, ignore_unknown_fields=True) - text = ('{\n' - ' "repeatedValue": [ {\n' - ' "@type": "type.googleapis.com/proto3.MessageType",\n' - ' "unknownName": 1\n' - ' }]\n' - '}\n') + text = ( + '{\n' + ' "repeatedValue": [ {\n' + ' "@type": "type.googleapis.com/proto3.MessageType",\n' + ' "unknownName": 1\n' + ' }]\n' + '}\n' + ) parsed_message = json_format_proto3_pb2.TestAny() json_format.Parse(text, parsed_message, ignore_unknown_fields=True) def testDuplicateField(self): - self.CheckError('{"int32Value": 1,\n"int32Value":2}', - 'Failed to load JSON: duplicate key int32Value.') + self.CheckError( + '{"int32Value": 1,\n"int32Value":2}', + 'Failed to load JSON: duplicate key int32Value.', + ) def testInvalidBoolValue(self): self.CheckError( - '{"boolValue": 1}', 'Failed to parse boolValue field: ' - 'Expected true or false without quotes at TestMessage.boolValue.') + '{"boolValue": 1}', + 'Failed to parse boolValue field: ' + 'Expected true or false without quotes at TestMessage.boolValue.', + ) self.CheckError( - '{"boolValue": "true"}', 'Failed to parse boolValue field: ' - 'Expected true or false without quotes at TestMessage.boolValue.') + '{"boolValue": "true"}', + 'Failed to parse boolValue field: ' + 'Expected true or false without quotes at TestMessage.boolValue.', + ) def testInvalidIntegerValue(self): message = json_format_proto3_pb2.TestMessage() text = '{"int32Value": 0x12345}' - self.assertRaises(json_format.ParseError, - json_format.Parse, text, message) + self.assertRaises(json_format.ParseError, json_format.Parse, text, message) self.CheckError( - '{"int32Value": 1.5}', 'Failed to parse int32Value field: ' - 'Couldn\'t parse integer: 1.5 at TestMessage.int32Value.') - self.CheckError('{"int32Value": 012345}', - (r'Failed to load JSON: Expecting \'?,\'? delimiter: ' - r'line 1.')) + '{"int32Value": 1.5}', + 'Failed to parse int32Value field: ' + "Couldn't parse integer: 1.5 at TestMessage.int32Value.", + ) self.CheckError( - '{"int32Value": " 1 "}', 'Failed to parse int32Value field: ' - 'Couldn\'t parse integer: " 1 " at TestMessage.int32Value.') + '{"int32Value": 012345}', + (r'Failed to load JSON: Expecting \'?,\'? delimiter: ' r'line 1.'), + ) self.CheckError( - '{"int32Value": "1 "}', 'Failed to parse int32Value field: ' - 'Couldn\'t parse integer: "1 " at TestMessage.int32Value.') + '{"int32Value": " 1 "}', + 'Failed to parse int32Value field: ' + 'Couldn\'t parse integer: " 1 " at TestMessage.int32Value.', + ) + self.CheckError( + '{"int32Value": "1 "}', + 'Failed to parse int32Value field: ' + 'Couldn\'t parse integer: "1 " at TestMessage.int32Value.', + ) self.CheckError( '{"int32Value": false}', 'Failed to parse int32Value field: Bool value False ' - 'is not acceptable for integer field at TestMessage.int32Value.') - self.CheckError('{"int32Value": 12345678901234567890}', - 'Failed to parse int32Value field: Value out of range: ' - '12345678901234567890.') - self.CheckError('{"uint32Value": -1}', - 'Failed to parse uint32Value field: ' - 'Value out of range: -1.') + 'is not acceptable for integer field at TestMessage.int32Value.', + ) + self.CheckError( + '{"int32Value": 12345678901234567890}', + 'Failed to parse int32Value field: Value out of range: ' + '12345678901234567890.', + ) + self.CheckError( + '{"uint32Value": -1}', + 'Failed to parse uint32Value field: Value out of range: -1.', + ) def testInvalidFloatValue(self): self.CheckError( - '{"floatValue": "nan"}', 'Failed to parse floatValue field: Couldn\'t ' - 'parse float "nan", use "NaN" instead at TestMessage.floatValue.') - self.CheckError('{"floatValue": NaN}', - 'Failed to parse floatValue field: Couldn\'t ' - 'parse NaN, use quoted "NaN" instead.') - self.CheckError('{"floatValue": Infinity}', - 'Failed to parse floatValue field: Couldn\'t parse Infinity' - ' or value too large, use quoted "Infinity" instead.') - self.CheckError('{"floatValue": -Infinity}', - 'Failed to parse floatValue field: Couldn\'t parse ' - '-Infinity or value too small, ' - 'use quoted "-Infinity" instead.') - self.CheckError('{"doubleValue": -1.89769e+308}', - 'Failed to parse doubleValue field: Couldn\'t parse ' - '-Infinity or value too small, ' - 'use quoted "-Infinity" instead.') - self.CheckError('{"floatValue": 3.4028235e+39}', - 'Failed to parse floatValue field: Float value too large.') - self.CheckError('{"floatValue": -3.502823e+38}', - 'Failed to parse floatValue field: Float value too small.') + '{"floatValue": "nan"}', + "Failed to parse floatValue field: Couldn't " + 'parse float "nan", use "NaN" instead at TestMessage.floatValue.', + ) + self.CheckError( + '{"floatValue": NaN}', + "Failed to parse floatValue field: Couldn't " + 'parse NaN, use quoted "NaN" instead.', + ) + self.CheckError( + '{"floatValue": Infinity}', + "Failed to parse floatValue field: Couldn't parse Infinity" + ' or value too large, use quoted "Infinity" instead.', + ) + self.CheckError( + '{"floatValue": -Infinity}', + "Failed to parse floatValue field: Couldn't parse " + '-Infinity or value too small, ' + 'use quoted "-Infinity" instead.', + ) + self.CheckError( + '{"doubleValue": -1.89769e+308}', + "Failed to parse doubleValue field: Couldn't parse " + '-Infinity or value too small, ' + 'use quoted "-Infinity" instead.', + ) + self.CheckError( + '{"floatValue": 3.4028235e+39}', + 'Failed to parse floatValue field: Float value too large.', + ) + self.CheckError( + '{"floatValue": -3.502823e+38}', + 'Failed to parse floatValue field: Float value too small.', + ) def testInvalidRepeated(self): self.CheckError( '{"repeatedInt32Value": 12345}', - (r'Failed to parse repeatedInt32Value field: repeated field' - r' repeatedInt32Value must be in \[\] which is 12345 at TestMessage.')) + ( + r'Failed to parse repeatedInt32Value field: repeated field' + r' repeatedInt32Value must be in \[\] which is 12345 at' + r' TestMessage.' + ), + ) def testInvalidMap(self): message = json_format_proto3_pb2.TestMap() text = '{"int32Map": {"null": 2, "2": 3}}' - self.assertRaisesRegex(json_format.ParseError, - 'Failed to parse int32Map field: invalid literal', - json_format.Parse, text, message) + self.assertRaisesRegex( + json_format.ParseError, + 'Failed to parse int32Map field: invalid literal', + json_format.Parse, + text, + message, + ) text = '{"int32Map": {1: 2, "2": 3}}' - self.assertRaisesRegex(json_format.ParseError, - (r'Failed to load JSON: Expecting property name' - r'( enclosed in double quotes)?: line 1'), - json_format.Parse, text, message) + self.assertRaisesRegex( + json_format.ParseError, + ( + r'Failed to load JSON: Expecting property name' + r'( enclosed in double quotes)?: line 1' + ), + json_format.Parse, + text, + message, + ) text = '{"boolMap": {"null": 1}}' self.assertRaisesRegex( json_format.ParseError, - 'Failed to parse boolMap field: Expected "true" or "false", not null at ' - 'TestMap.boolMap.key', json_format.Parse, text, message) + 'Failed to parse boolMap field: Expected "true" or "false", not null at' + ' TestMap.boolMap.key', + json_format.Parse, + text, + message, + ) text = r'{"stringMap": {"a": 3, "\u0061": 2}}' - self.assertRaisesRegex(json_format.ParseError, - 'Failed to load JSON: duplicate key a', - json_format.Parse, text, message) + self.assertRaisesRegex( + json_format.ParseError, + 'Failed to load JSON: duplicate key a', + json_format.Parse, + text, + message, + ) text = r'{"stringMap": 0}' self.assertRaisesRegex( json_format.ParseError, 'Failed to parse stringMap field: Map field string_map must be ' - 'in a dict which is 0 at TestMap.stringMap.', json_format.Parse, text, - message) + 'in a dict which is 0 at TestMap.stringMap.', + json_format.Parse, + text, + message, + ) def testInvalidTimestamp(self): message = json_format_proto3_pb2.TestTimestamp() text = '{"value": "10000-01-01T00:00:00.00Z"}' self.assertRaisesRegex( - json_format.ParseError, 'Failed to parse value field: ' - 'time data \'10000-01-01T00:00:00\' does not match' - ' format \'%Y-%m-%dT%H:%M:%S\' at TestTimestamp.value.', - json_format.Parse, text, message) + json_format.ParseError, + 'Failed to parse value field: ' + "time data '10000-01-01T00:00:00' does not match" + " format '%Y-%m-%dT%H:%M:%S' at TestTimestamp.value.", + json_format.Parse, + text, + message, + ) text = '{"value": "1970-01-01T00:00:00.0123456789012Z"}' self.assertRaisesRegex( json_format.ParseError, - 'nanos 0123456789012 more than 9 fractional digits.', json_format.Parse, - text, message) + 'nanos 0123456789012 more than 9 fractional digits.', + json_format.Parse, + text, + message, + ) text = '{"value": "1972-01-01T01:00:00.01+08"}' - self.assertRaisesRegex(json_format.ParseError, - (r'Invalid timezone offset value: \+08.'), - json_format.Parse, text, message) + self.assertRaisesRegex( + json_format.ParseError, + r'Invalid timezone offset value: \+08.', + json_format.Parse, + text, + message, + ) # Time smaller than minimum time. text = '{"value": "0000-01-01T00:00:00Z"}' self.assertRaisesRegex( json_format.ParseError, 'Failed to parse value field: year (0 )?is out of range.', - json_format.Parse, text, message) + json_format.Parse, + text, + message, + ) # Time bigger than maximum time. message.value.seconds = 253402300800 self.assertRaisesRegex(json_format.SerializeToJsonError, @@ -1066,26 +1262,35 @@ class JsonFormatTest(JsonFormatBase): # Nanos smaller than 0 message.value.seconds = 0 message.value.nanos = -1 - self.assertRaisesRegex(json_format.SerializeToJsonError, - 'Timestamp is not valid', - json_format.MessageToJson, message) + self.assertRaisesRegex( + json_format.SerializeToJsonError, + 'Timestamp is not valid', + json_format.MessageToJson, + message, + ) # Lower case t does not accept. text = '{"value": "0001-01-01t00:00:00Z"}' with self.assertRaises(json_format.ParseError) as e: json_format.Parse(text, message) self.assertEqual( 'Failed to parse value field: ' - 'time data \'0001-01-01t00:00:00\' does not match format ' - '\'%Y-%m-%dT%H:%M:%S\', lowercase \'t\' is not accepted ' - 'at TestTimestamp.value.', str(e.exception)) + "time data '0001-01-01t00:00:00' does not match format " + "'%Y-%m-%dT%H:%M:%S', lowercase 't' is not accepted " + 'at TestTimestamp.value.', + str(e.exception), + ) def testInvalidOneof(self): message = json_format_proto3_pb2.TestOneof() text = '{"oneofInt32Value": 1, "oneofStringValue": "2"}' self.assertRaisesRegex( - json_format.ParseError, 'Message type "proto3.TestOneof"' + json_format.ParseError, + 'Message type "proto3.TestOneof"' ' should not have multiple "oneof_value" oneof fields at "TestOneof".', - json_format.Parse, text, message) + json_format.Parse, + text, + message, + ) def testInvalidListValue(self): message = json_format_proto3_pb2.TestListValue() @@ -1093,17 +1298,24 @@ class JsonFormatTest(JsonFormatBase): self.assertRaisesRegex( json_format.ParseError, r'Failed to parse value field: ListValue must be in \[\] which is ' - '1234 at TestListValue.value.', json_format.Parse, text, message) + '1234 at TestListValue.value.', + json_format.Parse, + text, + message, + ) class UnknownClass(object): def __str__(self): return 'v' + self.assertRaisesRegex( json_format.ParseError, r' at TestListValue.value\[1\].fake.', json_format.ParseDict, - {'value': ['hello', {'fake': UnknownClass()}]}, message) + {'value': ['hello', {'fake': UnknownClass()}]}, + message, + ) def testInvalidStruct(self): message = json_format_proto3_pb2.TestStruct() @@ -1111,62 +1323,104 @@ class JsonFormatTest(JsonFormatBase): self.assertRaisesRegex( json_format.ParseError, 'Failed to parse value field: Struct must be in a dict which is ' - '1234 at TestStruct.value', json_format.Parse, text, message) + '1234 at TestStruct.value', + json_format.Parse, + text, + message, + ) def testTimestampInvalidStringValue(self): message = json_format_proto3_pb2.TestTimestamp() text = '{"value": {"foo": 123}}' self.assertRaisesRegex( json_format.ParseError, - r"Timestamp JSON value not a string: {u?'foo': 123}", json_format.Parse, - text, message) + r"Timestamp JSON value not a string: {u?'foo': 123}", + json_format.Parse, + text, + message, + ) def testDurationInvalidStringValue(self): message = json_format_proto3_pb2.TestDuration() text = '{"value": {"foo": 123}}' - self.assertRaisesRegex(json_format.ParseError, - r"Duration JSON value not a string: {u?'foo': 123}", - json_format.Parse, text, message) + self.assertRaisesRegex( + json_format.ParseError, + r"Duration JSON value not a string: {u?'foo': 123}", + json_format.Parse, + text, + message, + ) def testFieldMaskInvalidStringValue(self): message = json_format_proto3_pb2.TestFieldMask() text = '{"value": {"foo": 123}}' self.assertRaisesRegex( json_format.ParseError, - r"FieldMask JSON value not a string: {u?'foo': 123}", json_format.Parse, - text, message) + r"FieldMask JSON value not a string: {u?'foo': 123}", + json_format.Parse, + text, + message, + ) def testInvalidAny(self): message = any_pb2.Any() text = '{"@type": "type.googleapis.com/google.protobuf.Int32Value"}' self.assertRaisesRegex(KeyError, 'value', json_format.Parse, text, message) text = '{"value": 1234}' - self.assertRaisesRegex(json_format.ParseError, - '@type is missing when parsing any message at Any', - json_format.Parse, text, message) + self.assertRaisesRegex( + json_format.ParseError, + '@type is missing when parsing any message at Any', + json_format.Parse, + text, + message, + ) text = '{"@type": "type.googleapis.com/MessageNotExist", "value": 1234}' self.assertRaisesRegex( - json_format.ParseError, 'Can not find message descriptor by type_url: ' - 'type.googleapis.com/MessageNotExist at Any', json_format.Parse, text, - message) + json_format.ParseError, + 'Can not find message descriptor by type_url: ' + 'type.googleapis.com/MessageNotExist at Any', + json_format.Parse, + text, + message, + ) # Only last part is to be used: b/25630112 - text = (r'{"@type": "incorrect.googleapis.com/google.protobuf.Int32Value",' - r'"value": 1234}') + text = ( + r'{"@type": "incorrect.googleapis.com/google.protobuf.Int32Value",' + r'"value": 1234}' + ) json_format.Parse(text, message) def testPreservingProtoFieldNames(self): message = json_format_proto3_pb2.TestMessage() message.int32_value = 12345 - self.assertEqual('{\n "int32Value": 12345\n}', - json_format.MessageToJson(message)) - self.assertEqual('{\n "int32_value": 12345\n}', - json_format.MessageToJson(message, False, True)) - # When including_default_value_fields is True. + self.assertEqual( + '{\n "int32Value": 12345\n}', json_format.MessageToJson(message) + ) + self.assertEqual( + '{\n "int32_value": 12345\n}', + json_format.MessageToJson( + message, + always_print_fields_with_no_presence=False, + preserving_proto_field_name=True, + ), + ) + # When always_print_fields_with_no_presence is True. message = json_format_proto3_pb2.TestTimestamp() - self.assertEqual('{\n "repeatedValue": []\n}', - json_format.MessageToJson(message, True, False)) - self.assertEqual('{\n "repeated_value": []\n}', - json_format.MessageToJson(message, True, True)) + self.assertEqual( + '{\n "repeatedValue": []\n}', + json_format.MessageToJson( + message, + always_print_fields_with_no_presence=True, + ), + ) + self.assertEqual( + '{\n "repeated_value": []\n}', + json_format.MessageToJson( + message, + always_print_fields_with_no_presence=True, + preserving_proto_field_name=True, + ), + ) # Parsers accept both original proto field names and lowerCamelCase names. message = json_format_proto3_pb2.TestMessage() @@ -1178,20 +1432,22 @@ class JsonFormatTest(JsonFormatBase): def testIndent(self): message = json_format_proto3_pb2.TestMessage() message.int32_value = 12345 - self.assertEqual('{\n"int32Value": 12345\n}', - json_format.MessageToJson(message, indent=0)) + self.assertEqual( + '{\n"int32Value": 12345\n}', + json_format.MessageToJson(message, indent=0), + ) def testFormatEnumsAsInts(self): message = json_format_proto3_pb2.TestMessage() message.enum_value = json_format_proto3_pb2.BAR message.repeated_enum_value.append(json_format_proto3_pb2.FOO) message.repeated_enum_value.append(json_format_proto3_pb2.BAR) - self.assertEqual(json.loads('{\n' - ' "enumValue": 1,\n' - ' "repeatedEnumValue": [0, 1]\n' - '}\n'), - json.loads(json_format.MessageToJson( - message, use_integers_for_enums=True))) + self.assertEqual( + json.loads('{\n "enumValue": 1,\n "repeatedEnumValue": [0, 1]\n}\n'), + json.loads( + json_format.MessageToJson(message, use_integers_for_enums=True) + ), + ) def testParseDict(self): expected = 12345 @@ -1205,7 +1461,7 @@ class JsonFormatTest(JsonFormatBase): js_dict = { 'any_value': { '@type': 'type.googleapis.com/proto3.MessageType', - 'value': 1234 + 'value': 1234, } } json_format.ParseDict(js_dict, any_test_pb2.TestAny()) @@ -1213,19 +1469,19 @@ class JsonFormatTest(JsonFormatBase): js_dict = { 'any_value': { '@type': 'type.googleapis.com/proto3.MessageType', - 'value': 1234 + 'value': 1234, } } with self.assertRaises(json_format.ParseError) as cm: empty_pool = descriptor_pool.DescriptorPool() - json_format.ParseDict(js_dict, - any_test_pb2.TestAny(), - descriptor_pool=empty_pool) + json_format.ParseDict( + js_dict, any_test_pb2.TestAny(), descriptor_pool=empty_pool + ) self.assertEqual( str(cm.exception), 'Failed to parse any_value field: Can not find message descriptor by' ' type_url: type.googleapis.com/proto3.MessageType at ' - 'TestAny.any_value.' + 'TestAny.any_value.', ) def testParseDictUnknownValueType(self): @@ -1233,42 +1489,57 @@ class JsonFormatTest(JsonFormatBase): def __repr__(self): return 'v' + message = json_format_proto3_pb2.TestValue() self.assertRaisesRegex( json_format.ParseError, r"Value v has unexpected type .", - json_format.ParseDict, {'value': UnknownClass()}, message) + json_format.ParseDict, + {'value': UnknownClass()}, + message, + ) def testMessageToDict(self): message = json_format_proto3_pb2.TestMessage() message.int32_value = 12345 expected = {'int32Value': 12345} - self.assertEqual(expected, - json_format.MessageToDict(message)) + self.assertEqual(expected, json_format.MessageToDict(message)) def testJsonName(self): message = json_format_proto3_pb2.TestCustomJsonName() message.value = 12345 - self.assertEqual('{\n "@value": 12345\n}', - json_format.MessageToJson(message)) + self.assertEqual( + '{\n "@value": 12345\n}', json_format.MessageToJson(message) + ) parsed_message = json_format_proto3_pb2.TestCustomJsonName() self.CheckParseBack(message, parsed_message) def testSortKeys(self): # Testing sort_keys is not perfectly working, as by random luck we could # get the output sorted. We just use a selection of names. - message = json_format_proto3_pb2.TestMessage(bool_value=True, - int32_value=1, - int64_value=3, - uint32_value=4, - string_value='bla') + message = json_format_proto3_pb2.TestMessage( + bool_value=True, + int32_value=1, + int64_value=3, + uint32_value=4, + string_value='bla', + ) self.assertEqual( json_format.MessageToJson(message, sort_keys=True), # We use json.dumps() instead of a hardcoded string due to differences # between Python 2 and Python 3. - json.dumps({'boolValue': True, 'int32Value': 1, 'int64Value': '3', - 'uint32Value': 4, 'stringValue': 'bla'}, - indent=2, sort_keys=True)) + json.dumps( + { + 'boolValue': True, + 'int32Value': 1, + 'int64Value': '3', + 'uint32Value': 4, + 'stringValue': 'bla', + }, + indent=2, + sort_keys=True, + ), + ) def testNestedRecursiveLimit(self): message = unittest_pb2.NestedTestAllTypes() @@ -1278,22 +1549,24 @@ class JsonFormatTest(JsonFormatBase): json_format.Parse, '{"child": {"child": {"child" : {}}}}', message, - max_recursion_depth=3) + max_recursion_depth=3, + ) # The following one can pass - json_format.Parse('{"payload": {}, "child": {"child":{}}}', - message, max_recursion_depth=3) + json_format.Parse( + '{"payload": {}, "child": {"child":{}}}', message, max_recursion_depth=3 + ) def testJsonNameConflictSerilize(self): message = more_messages_pb2.ConflictJsonName(value=2) self.assertEqual( json.loads('{"old_value": 2}'), - json.loads(json_format.MessageToJson(message)) + json.loads(json_format.MessageToJson(message)), ) new_message = more_messages_pb2.ConflictJsonName(new_value=2) self.assertEqual( json.loads('{"value": 2}'), - json.loads(json_format.MessageToJson(new_message)) + json.loads(json_format.MessageToJson(new_message)), ) def testJsonNameConflictParse(self): diff --git a/python/google/protobuf/internal/message_test.py b/python/google/protobuf/internal/message_test.py index 94347aaf1d..998b0444a0 100755 --- a/python/google/protobuf/internal/message_test.py +++ b/python/google/protobuf/internal/message_test.py @@ -1777,7 +1777,8 @@ class Proto3Test(unittest.TestCase): # Test has presence: for field in test_proto3_optional_pb2.TestProto3Optional.DESCRIPTOR.fields: - self.assertTrue(field.has_presence) + if field.name.startswith('optional_'): + self.assertTrue(field.has_presence) for field in unittest_pb2.TestAllTypes.DESCRIPTOR.fields: if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: self.assertFalse(field.has_presence) diff --git a/python/google/protobuf/internal/test_proto2.proto b/python/google/protobuf/internal/test_proto2.proto new file mode 100644 index 0000000000..9c1022eacb --- /dev/null +++ b/python/google/protobuf/internal/test_proto2.proto @@ -0,0 +1,39 @@ +// 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 google.protobuf.python.internal; + +message TestProto2 { + message NestedMessage { + // The field name "b" fails to compile in proto1 because it conflicts with + // a local variable named "b" in one of the generated methods. Doh. + // This file needs to compile in proto1 to test backwards-compatibility. + optional int32 bb = 1; + } + + enum NestedEnum { + UNSPECIFIED = 0; + FOO = 1; + BAR = 2; + BAZ = 3; + NEG = -1; // Intentionally negative. + } + + optional int32 optional_int32 = 1; + 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; + + repeated int32 repeated_int32 = 22; + repeated NestedMessage repeated_nested_message = 23; +} diff --git a/python/google/protobuf/internal/test_proto3_optional.proto b/python/google/protobuf/internal/test_proto3_optional.proto index 6ad3d086c3..cc2e988540 100644 --- a/python/google/protobuf/internal/test_proto3_optional.proto +++ b/python/google/protobuf/internal/test_proto3_optional.proto @@ -44,4 +44,7 @@ message TestProto3Optional { optional NestedMessage optional_nested_message = 18; optional NestedEnum optional_nested_enum = 21; + + repeated int32 repeated_int32 = 22; + repeated NestedMessage repeated_nested_message = 23; } diff --git a/python/google/protobuf/json_format.py b/python/google/protobuf/json_format.py index 1b6ce9d03d..79f5078fa5 100644 --- a/python/google/protobuf/json_format.py +++ b/python/google/protobuf/json_format.py @@ -27,26 +27,33 @@ import math from operator import methodcaller import re -from google.protobuf.internal import type_checkers from google.protobuf import descriptor from google.protobuf import message_factory from google.protobuf import symbol_database +from google.protobuf.internal import type_checkers -_INT_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_INT32, - descriptor.FieldDescriptor.CPPTYPE_UINT32, - descriptor.FieldDescriptor.CPPTYPE_INT64, - descriptor.FieldDescriptor.CPPTYPE_UINT64]) -_INT64_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_INT64, - descriptor.FieldDescriptor.CPPTYPE_UINT64]) -_FLOAT_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_FLOAT, - descriptor.FieldDescriptor.CPPTYPE_DOUBLE]) +_INT_TYPES = frozenset([ + descriptor.FieldDescriptor.CPPTYPE_INT32, + descriptor.FieldDescriptor.CPPTYPE_UINT32, + descriptor.FieldDescriptor.CPPTYPE_INT64, + descriptor.FieldDescriptor.CPPTYPE_UINT64, +]) +_INT64_TYPES = frozenset([ + descriptor.FieldDescriptor.CPPTYPE_INT64, + descriptor.FieldDescriptor.CPPTYPE_UINT64, +]) +_FLOAT_TYPES = frozenset([ + descriptor.FieldDescriptor.CPPTYPE_FLOAT, + descriptor.FieldDescriptor.CPPTYPE_DOUBLE, +]) _INFINITY = 'Infinity' _NEG_INFINITY = '-Infinity' _NAN = 'NaN' _UNPAIRED_SURROGATE_PATTERN = re.compile( - u'[\ud800-\udbff](?![\udc00-\udfff])|(? self.max_recursion_depth: - raise ParseError('Message too deep. Max recursion depth is {0}'.format( - self.max_recursion_depth)) + raise ParseError( + 'Message too deep. Max recursion depth is {0}'.format( + self.max_recursion_depth + ) + ) message_descriptor = message.DESCRIPTOR full_name = message_descriptor.full_name if not path: @@ -500,8 +538,9 @@ class _Parser(object): """ names = [] message_descriptor = message.DESCRIPTOR - fields_by_json_name = dict((f.json_name, f) - for f in message_descriptor.fields) + fields_by_json_name = dict( + (f.json_name, f) for f in message_descriptor.fields + ) for name in js: try: field = fields_by_json_name.get(name, None) @@ -511,7 +550,9 @@ class _Parser(object): if not message_descriptor.is_extendable: raise ParseError( 'Message type {0} does not have extensions at {1}'.format( - message_descriptor.full_name, path)) + message_descriptor.full_name, path + ) + ) identifier = name[1:-1] # strip [] brackets # pylint: disable=protected-access field = message.Extensions._FindExtensionByName(identifier) @@ -527,33 +568,48 @@ class _Parser(object): if self.ignore_unknown_fields: continue raise ParseError( - ('Message type "{0}" has no field named "{1}" at "{2}".\n' - ' Available Fields(except extensions): "{3}"').format( - message_descriptor.full_name, name, path, - [f.json_name for f in message_descriptor.fields])) + ( + 'Message type "{0}" has no field named "{1}" at "{2}".\n' + ' Available Fields(except extensions): "{3}"' + ).format( + message_descriptor.full_name, + name, + path, + [f.json_name for f in message_descriptor.fields], + ) + ) if name in names: - raise ParseError('Message type "{0}" should not have multiple ' - '"{1}" fields at "{2}".'.format( - message.DESCRIPTOR.full_name, name, path)) + raise ParseError( + 'Message type "{0}" should not have multiple ' + '"{1}" fields at "{2}".'.format( + message.DESCRIPTOR.full_name, name, path + ) + ) names.append(name) value = js[name] # Check no other oneof field is parsed. if field.containing_oneof is not None and value is not None: oneof_name = field.containing_oneof.name if oneof_name in names: - raise ParseError('Message type "{0}" should not have multiple ' - '"{1}" oneof fields at "{2}".'.format( - message.DESCRIPTOR.full_name, oneof_name, - path)) + raise ParseError( + 'Message type "{0}" should not have multiple ' + '"{1}" oneof fields at "{2}".'.format( + message.DESCRIPTOR.full_name, oneof_name, path + ) + ) names.append(oneof_name) if value is None: - if (field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE - and field.message_type.full_name == 'google.protobuf.Value'): + if ( + field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE + and field.message_type.full_name == 'google.protobuf.Value' + ): sub_message = getattr(message, field.name) sub_message.null_value = 0 - elif (field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM - and field.enum_type.full_name == 'google.protobuf.NullValue'): + elif ( + field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM + and field.enum_type.full_name == 'google.protobuf.NullValue' + ): setattr(message, field.name, 0) else: message.ClearField(field.name) @@ -562,35 +618,51 @@ class _Parser(object): # Parse field value. if _IsMapEntry(field): message.ClearField(field.name) - self._ConvertMapFieldValue(value, message, field, - '{0}.{1}'.format(path, name)) + self._ConvertMapFieldValue( + value, message, field, '{0}.{1}'.format(path, name) + ) elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: message.ClearField(field.name) if not isinstance(value, list): - raise ParseError('repeated field {0} must be in [] which is ' - '{1} at {2}'.format(name, value, path)) + raise ParseError( + 'repeated field {0} must be in [] which is {1} at {2}'.format( + name, value, path + ) + ) if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: # Repeated message field. for index, item in enumerate(value): sub_message = getattr(message, field.name).add() # None is a null_value in Value. - if (item is None and - sub_message.DESCRIPTOR.full_name != 'google.protobuf.Value'): - raise ParseError('null is not allowed to be used as an element' - ' in a repeated field at {0}.{1}[{2}]'.format( - path, name, index)) - self.ConvertMessage(item, sub_message, - '{0}.{1}[{2}]'.format(path, name, index)) + if ( + item is None + and sub_message.DESCRIPTOR.full_name + != 'google.protobuf.Value' + ): + raise ParseError( + 'null is not allowed to be used as an element' + ' in a repeated field at {0}.{1}[{2}]'.format( + path, name, index + ) + ) + self.ConvertMessage( + item, sub_message, '{0}.{1}[{2}]'.format(path, name, index) + ) else: # Repeated scalar field. for index, item in enumerate(value): if item is None: - raise ParseError('null is not allowed to be used as an element' - ' in a repeated field at {0}.{1}[{2}]'.format( - path, name, index)) + raise ParseError( + 'null is not allowed to be used as an element' + ' in a repeated field at {0}.{1}[{2}]'.format( + path, name, index + ) + ) getattr(message, field.name).append( _ConvertScalarFieldValue( - item, field, '{0}.{1}[{2}]'.format(path, name, index))) + item, field, '{0}.{1}[{2}]'.format(path, name, index) + ) + ) elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: if field.is_extension: sub_message = message.Extensions[field] @@ -601,26 +673,30 @@ class _Parser(object): else: if field.is_extension: message.Extensions[field] = _ConvertScalarFieldValue( - value, field, '{0}.{1}'.format(path, name)) + value, field, '{0}.{1}'.format(path, name) + ) else: setattr( - message, field.name, - _ConvertScalarFieldValue(value, field, - '{0}.{1}'.format(path, name))) + message, + field.name, + _ConvertScalarFieldValue( + value, field, '{0}.{1}'.format(path, name) + ), + ) except ParseError as e: if field and field.containing_oneof is None: raise ParseError( - 'Failed to parse {0} field: {1}.'.format(name, e) + 'Failed to parse {0} field: {1}.'.format(name, e) ) from e else: raise ParseError(str(e)) from e except ValueError as e: raise ParseError( - 'Failed to parse {0} field: {1}.'.format(name, e) + 'Failed to parse {0} field: {1}.'.format(name, e) ) from e except TypeError as e: raise ParseError( - 'Failed to parse {0} field: {1}.'.format(name, e) + 'Failed to parse {0} field: {1}.'.format(name, e) ) from e def _ConvertAnyMessage(self, value, message, path): @@ -631,7 +707,7 @@ class _Parser(object): type_url = value['@type'] except KeyError as e: raise ParseError( - '@type is missing when parsing any message at {0}'.format(path) + '@type is missing when parsing any message at {0}'.format(path) ) from e try: @@ -641,12 +717,16 @@ class _Parser(object): message_descriptor = sub_message.DESCRIPTOR full_name = message_descriptor.full_name if _IsWrapperMessage(message_descriptor): - self._ConvertWrapperMessage(value['value'], sub_message, - '{0}.value'.format(path)) + self._ConvertWrapperMessage( + value['value'], sub_message, '{0}.value'.format(path) + ) elif full_name in _WKTJSONMETHODS: - methodcaller(_WKTJSONMETHODS[full_name][1], value['value'], sub_message, - '{0}.value'.format(path))( - self) + methodcaller( + _WKTJSONMETHODS[full_name][1], + value['value'], + sub_message, + '{0}.value'.format(path), + )(self) else: del value['@type'] self._ConvertFieldValuePair(value, sub_message, path) @@ -679,38 +759,47 @@ class _Parser(object): elif isinstance(value, _INT_OR_FLOAT): message.number_value = value else: - raise ParseError('Value {0} has unexpected type {1} at {2}'.format( - value, type(value), path)) + raise ParseError( + 'Value {0} has unexpected type {1} at {2}'.format( + value, type(value), path + ) + ) def _ConvertListValueMessage(self, value, message, path): """Convert a JSON representation into ListValue message.""" if not isinstance(value, list): - raise ParseError('ListValue must be in [] which is {0} at {1}'.format( - value, path)) + raise ParseError( + 'ListValue must be in [] which is {0} at {1}'.format(value, path) + ) message.ClearField('values') for index, item in enumerate(value): - self._ConvertValueMessage(item, message.values.add(), - '{0}[{1}]'.format(path, index)) + self._ConvertValueMessage( + item, message.values.add(), '{0}[{1}]'.format(path, index) + ) def _ConvertStructMessage(self, value, message, path): """Convert a JSON representation into Struct message.""" if not isinstance(value, dict): - raise ParseError('Struct must be in a dict which is {0} at {1}'.format( - value, path)) + raise ParseError( + 'Struct must be in a dict which is {0} at {1}'.format(value, path) + ) # Clear will mark the struct as modified so it will be created even if # there are no values. message.Clear() for key in value: - self._ConvertValueMessage(value[key], message.fields[key], - '{0}.{1}'.format(path, key)) + self._ConvertValueMessage( + value[key], message.fields[key], '{0}.{1}'.format(path, key) + ) return def _ConvertWrapperMessage(self, value, message, path): """Convert a JSON representation into Wrapper message.""" field = message.DESCRIPTOR.fields_by_name['value'] setattr( - message, 'value', - _ConvertScalarFieldValue(value, field, path='{0}.value'.format(path))) + message, + 'value', + _ConvertScalarFieldValue(value, field, path='{0}.value'.format(path)), + ) def _ConvertMapFieldValue(self, value, message, field, path): """Convert map field value for a message map field. @@ -727,19 +816,25 @@ class _Parser(object): if not isinstance(value, dict): raise ParseError( 'Map field {0} must be in a dict which is {1} at {2}'.format( - field.name, value, path)) + field.name, value, path + ) + ) key_field = field.message_type.fields_by_name['key'] value_field = field.message_type.fields_by_name['value'] for key in value: - key_value = _ConvertScalarFieldValue(key, key_field, - '{0}.key'.format(path), True) + key_value = _ConvertScalarFieldValue( + key, key_field, '{0}.key'.format(path), True + ) if value_field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: - self.ConvertMessage(value[key], - getattr(message, field.name)[key_value], - '{0}[{1}]'.format(path, key_value)) + self.ConvertMessage( + value[key], + getattr(message, field.name)[key_value], + '{0}[{1}]'.format(path, key_value), + ) else: getattr(message, field.name)[key_value] = _ConvertScalarFieldValue( - value[key], value_field, path='{0}[{1}]'.format(path, key_value)) + value[key], value_field, path='{0}[{1}]'.format(path, key_value) + ) def _ConvertScalarFieldValue(value, field, path, require_str=False): @@ -787,12 +882,18 @@ def _ConvertScalarFieldValue(value, field, path, require_str=False): number = int(value) enum_value = field.enum_type.values_by_number.get(number, None) except ValueError as e: - raise ParseError('Invalid enum value {0} for enum type {1}'.format( - value, field.enum_type.full_name)) from e + raise ParseError( + 'Invalid enum value {0} for enum type {1}'.format( + value, field.enum_type.full_name + ) + ) from e if enum_value is None: if field.enum_type.is_closed: - raise ParseError('Invalid enum value {0} for enum type {1}'.format( - value, field.enum_type.full_name)) + raise ParseError( + 'Invalid enum value {0} for enum type {1}'.format( + value, field.enum_type.full_name + ) + ) else: return number return enum_value.number @@ -813,14 +914,15 @@ def _ConvertInteger(value): ParseError: If an integer couldn't be consumed. """ if isinstance(value, float) and not value.is_integer(): - raise ParseError('Couldn\'t parse integer: {0}'.format(value)) + raise ParseError("Couldn't parse integer: {0}".format(value)) if isinstance(value, str) and value.find(' ') != -1: raise ParseError('Couldn\'t parse integer: "{0}"'.format(value)) if isinstance(value, bool): - raise ParseError('Bool value {0} is not acceptable for ' - 'integer field'.format(value)) + raise ParseError( + 'Bool value {0} is not acceptable for integer field'.format(value) + ) return int(value) @@ -832,11 +934,15 @@ def _ConvertFloat(value, field): raise ParseError('Couldn\'t parse NaN, use quoted "NaN" instead') if math.isinf(value): if value > 0: - raise ParseError('Couldn\'t parse Infinity or value too large, ' - 'use quoted "Infinity" instead') + raise ParseError( + "Couldn't parse Infinity or value too large, " + 'use quoted "Infinity" instead' + ) else: - raise ParseError('Couldn\'t parse -Infinity or value too small, ' - 'use quoted "-Infinity" instead') + raise ParseError( + "Couldn't parse -Infinity or value too small, " + 'use quoted "-Infinity" instead' + ) if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_FLOAT: # pylint: disable=protected-access if value > type_checkers._FLOAT_MAX: @@ -858,7 +964,7 @@ def _ConvertFloat(value, field): elif value == _NAN: return float('nan') else: - raise ParseError('Couldn\'t parse float: {0}'.format(value)) from e + raise ParseError("Couldn't parse float: {0}".format(value)) from e def _ConvertBool(value, require_str): @@ -886,19 +992,31 @@ def _ConvertBool(value, require_str): raise ParseError('Expected true or false without quotes') return value + _WKTJSONMETHODS = { - 'google.protobuf.Any': ['_AnyMessageToJsonObject', - '_ConvertAnyMessage'], - 'google.protobuf.Duration': ['_GenericMessageToJsonObject', - '_ConvertGenericMessage'], - 'google.protobuf.FieldMask': ['_GenericMessageToJsonObject', - '_ConvertGenericMessage'], - 'google.protobuf.ListValue': ['_ListValueMessageToJsonObject', - '_ConvertListValueMessage'], - 'google.protobuf.Struct': ['_StructMessageToJsonObject', - '_ConvertStructMessage'], - 'google.protobuf.Timestamp': ['_GenericMessageToJsonObject', - '_ConvertGenericMessage'], - 'google.protobuf.Value': ['_ValueMessageToJsonObject', - '_ConvertValueMessage'] + 'google.protobuf.Any': ['_AnyMessageToJsonObject', '_ConvertAnyMessage'], + 'google.protobuf.Duration': [ + '_GenericMessageToJsonObject', + '_ConvertGenericMessage', + ], + 'google.protobuf.FieldMask': [ + '_GenericMessageToJsonObject', + '_ConvertGenericMessage', + ], + 'google.protobuf.ListValue': [ + '_ListValueMessageToJsonObject', + '_ConvertListValueMessage', + ], + 'google.protobuf.Struct': [ + '_StructMessageToJsonObject', + '_ConvertStructMessage', + ], + 'google.protobuf.Timestamp': [ + '_GenericMessageToJsonObject', + '_ConvertGenericMessage', + ], + 'google.protobuf.Value': [ + '_ValueMessageToJsonObject', + '_ConvertValueMessage', + ], } diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java index f8c3950237..2b533dfc45 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java @@ -700,7 +700,7 @@ public class RubyMessage extends RubyObject { options.fastARef(runtime.newSymbol("format_enums_as_integers")); if (emitDefaults != null && emitDefaults.isTrue()) { - printer = printer.includingDefaultValueFields(); + printer = printer.alwaysPrintFieldsWithNoPresence(); } if (preserveNames != null && preserveNames.isTrue()) { diff --git a/src/google/protobuf/json/internal/unparser.cc b/src/google/protobuf/json/internal/unparser.cc index 989b86e844..33e4505d13 100644 --- a/src/google/protobuf/json/internal/unparser.cc +++ b/src/google/protobuf/json/internal/unparser.cc @@ -433,8 +433,6 @@ absl::Status WriteField(JsonWriter& writer, const Msg& msg, } else if (Traits::IsRepeated(field)) { return WriteRepeated(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); if (Traits::FieldType(field) == FieldDescriptor::TYPE_GROUP) { // We do not yet have full group support, but this is required so that we @@ -458,11 +456,9 @@ absl::Status WriteFields(JsonWriter& writer, const Msg& msg, Field field = Traits::FieldByIndex(desc, i); bool has = Traits::GetSize(field, msg) > 0; - if (writer.options().always_print_primitive_fields) { - bool is_singular_message = - !Traits::IsRepeated(field) && - Traits::FieldType(field) == FieldDescriptor::TYPE_MESSAGE; - has |= !is_singular_message && !Traits::IsOneof(field); + + if (writer.options().always_print_fields_with_no_presence) { + has |= Traits::IsRepeated(field) || Traits::IsImplicitPresence(field); } if (has) { diff --git a/src/google/protobuf/json/internal/writer.h b/src/google/protobuf/json/internal/writer.h index 16357a798c..ba4139f8b7 100644 --- a/src/google/protobuf/json/internal/writer.h +++ b/src/google/protobuf/json/internal/writer.h @@ -36,11 +36,11 @@ 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. - 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_fields_with_no_presence = false; // Whether to always print enums as ints. By default they are rendered as // strings. bool always_print_enums_as_ints = false; diff --git a/src/google/protobuf/json/json.cc b/src/google/protobuf/json/json.cc index c7d0d286bb..cd8743d151 100644 --- a/src/google/protobuf/json/json.cc +++ b/src/google/protobuf/json/json.cc @@ -33,7 +33,8 @@ absl::Status BinaryToJsonStream(google::protobuf::util::TypeResolver* resolver, opts.add_whitespace = options.add_whitespace; 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_fields_with_no_presence = + options.always_print_fields_with_no_presence; opts.unquote_int64_if_possible = options.unquote_int64_if_possible; // TODO: Drop this setting. @@ -87,7 +88,8 @@ absl::Status MessageToJsonString(const Message& message, std::string* output, opts.add_whitespace = options.add_whitespace; 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_fields_with_no_presence = + options.always_print_fields_with_no_presence; opts.unquote_int64_if_possible = options.unquote_int64_if_possible; // TODO: Drop this setting. diff --git a/src/google/protobuf/json/json.h b/src/google/protobuf/json/json.h index d8b40fe778..c025a2a5a8 100644 --- a/src/google/protobuf/json/json.h +++ b/src/google/protobuf/json/json.h @@ -39,11 +39,11 @@ 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. - 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_fields_with_no_presence = false; // Whether to always print enums as ints. By default they are rendered as // strings. bool always_print_enums_as_ints = false; diff --git a/src/google/protobuf/json/json_test.cc b/src/google/protobuf/json/json_test.cc index aac3ed7dd0..b656ec00ce 100644 --- a/src/google/protobuf/json/json_test.cc +++ b/src/google/protobuf/json/json_test.cc @@ -173,33 +173,34 @@ TEST_P(JsonTest, TestWhitespaces) { )")); } -TEST_P(JsonTest, TestDefaultValues) { + +TEST_P(JsonTest, TestAlwaysPrintFieldsWithNoPresence) { TestMessage m; EXPECT_THAT(ToJson(m), IsOkAndHolds("{}")); 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\":[]" + options.always_print_fields_with_no_presence = 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"); @@ -207,71 +208,43 @@ TEST_P(JsonTest, TestDefaultValues) { 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), IsOkAndHolds( - R"({"optionalInt32":0,"optionalInt64":"0","optionalUint32":0,)" - R"("optionalUint64":"0","optionalSint32":0,"optionalSint64":"0","optionalFixed32":0,)" - R"("optionalFixed64":"0","optionalSfixed32":0,"optionalSfixed64":"0",)" - R"("optionalFloat":0,"optionalDouble":0,"optionalBool":false,"optionalString":"",)" - R"("optionalBytes":"","optionalgroup":null,"optionalNestedEnum":"FOO","optionalForeignEnum":"FOREIGN_FOO",)" - R"("optionalImportEnum":"IMPORT_FOO","optionalStringPiece":"","optionalCord":"",)" - R"("repeatedInt32":[],"repeatedInt64":[],"repeatedUint32":[],"repeatedUint64":[],)" + 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":[],"defaultInt32":41,)" - R"("defaultInt64":"42","defaultUint32":43,"defaultUint64":"44","defaultSint32":-45,)" - R"("defaultSint64":"46","defaultFixed32":47,"defaultFixed64":"48","defaultSfixed32":49,)" - R"("defaultSfixed64":"-50","defaultFloat":51.5,"defaultDouble":52000,"defaultBool":true,)" - R"("defaultString":"hello","defaultBytes":"d29ybGQ=","defaultNestedEnum":"BAR",)" - R"("defaultForeignEnum":"FOREIGN_BAR","defaultImportEnum":"IMPORT_BAR",)" - R"("defaultStringPiece":"abc","defaultCord":"123"})")); - - EXPECT_THAT( - ToJson(protobuf_unittest::TestExtremeDefaultValues(), options), - IsOkAndHolds( - R"({"escapedBytes":"XDAwMFwwMDFcMDA3XDAxMFwwMTRcblxyXHRcMDEzXFxcJ1wiXDM3Ng==")" - R"(,"largeUint32":4294967295,"largeUint64":"18446744073709551615",)" - R"("smallInt32":-2147483647,"smallInt64":"-9223372036854775807",)" - R"("utf8String":"ሴ","zeroFloat":0,"oneFloat":1,"smallFloat":1.5,)" - R"("negativeOneFloat":-1,"negativeFloat":-1.5,"largeFloat":2e+08,)" - R"("smallNegativeFloat":-8e-28,"infDouble":0,"negInfDouble":0,)" - R"("nanDouble":0,"infFloat":0,"negInfFloat":0,"nanFloat":0,)" - R"("cppTrigraph":"? ? ?? ?? ??? ??/ ??-","reallySmallInt32":-2147483648)" - R"(,"reallySmallInt64":"-9223372036854775808","stringWithZero":"hel\u0000lo")" - R"(,"bytesWithZero":"d29yXDAwMGxk","stringPieceWithZero":"ab\u0000c")" - R"(,"cordWithZero":"12\u00003","replacementString":"${unknown}"})")); + R"("repeatedStringPiece":[],"repeatedCord":[],"repeatedLazyMessage":[]})")); } TEST_P(JsonTest, TestPreserveProtoFieldNames) { @@ -351,7 +324,7 @@ TEST_P(JsonTest, TestPrintEnumsAsIntsWithDefaultValue) { PrintOptions print_options; print_options.always_print_enums_as_ints = true; - print_options.always_print_primitive_fields = true; + print_options.always_print_fields_with_no_presence = true; auto printed = ToJson(orig, print_options); ASSERT_THAT( @@ -365,21 +338,6 @@ TEST_P(JsonTest, TestPrintEnumsAsIntsWithDefaultValue) { EXPECT_EQ(parsed->enum_value3(), proto3::BAR); } -TEST_P(JsonTest, TestPrintProto2EnumAsIntWithDefaultValue) { - protobuf_unittest::TestDefaultEnumValue orig; - - PrintOptions print_options; - print_options.always_print_enums_as_ints = true; - print_options.always_print_primitive_fields = true; - - auto printed = ToJson(orig, print_options); - ASSERT_THAT(printed, IsOkAndHolds("{\"enumValue\":2}")); - - auto parsed = ToProto(*printed); - ASSERT_OK(parsed); - - EXPECT_EQ(parsed->enum_value(), protobuf_unittest::DEFAULT); -} TEST_P(JsonTest, QuotedEnumValue) { auto m = ToProto(R"json( @@ -562,7 +520,7 @@ TEST_P(JsonTest, RepeatedMapKey) { TEST_P(JsonTest, ParsePrimitiveMapIn) { MapIn message; PrintOptions print_options; - print_options.always_print_primitive_fields = true; + print_options.always_print_fields_with_no_presence = true; auto printed = ToJson(message, print_options); ASSERT_THAT( ToJson(message, print_options), @@ -576,7 +534,7 @@ TEST_P(JsonTest, ParsePrimitiveMapIn) { TEST_P(JsonTest, PrintPrimitiveOneof) { TestOneof message; PrintOptions options; - options.always_print_primitive_fields = true; + options.always_print_fields_with_no_presence = true; message.mutable_oneof_message_value(); EXPECT_THAT(ToJson(message, options), IsOkAndHolds(R"({"oneofMessageValue":{"value":0}})")); @@ -1303,15 +1261,6 @@ TEST_P(JsonTest, HtmlEscape) { EXPECT_THAT(ToJson(m), IsOkAndHolds(R"({"stringValue":"\u003c/script\u003e"})")); - proto3::TestEvilJson m2; - PrintOptions opts; - opts.always_print_primitive_fields = true; - EXPECT_THAT( - ToJson(m2, opts), - IsOkAndHolds( - R"({"regular_name":0,"\u003c/script\u003e":0,)" - R"("unbalanced\"quotes":0,)" - R"("\"\u003cscript\u003ealert('hello!);\u003c/script\u003e":0})")); } TEST_P(JsonTest, FieldOrder) {