Merge pull request #15730 from protocolbuffers/cp-json-default-flag

Cherry-pick JSON formatter option changes
pull/15748/head
zhangskz 1 year ago committed by GitHub
commit 59f3a9da5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 184
      java/util/src/main/java/com/google/protobuf/util/JsonFormat.java
  2. 186
      java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java
  3. 6
      java/util/src/test/proto/com/google/protobuf/util/json_test.proto
  4. 82
      java/util/src/test/proto/com/google/protobuf/util/json_test_proto2.proto
  5. 927
      python/google/protobuf/internal/json_format_test.py
  6. 1
      python/google/protobuf/internal/message_test.py
  7. 39
      python/google/protobuf/internal/test_proto2.proto
  8. 3
      python/google/protobuf/internal/test_proto3_optional.proto
  9. 440
      python/google/protobuf/json_format.py
  10. 2
      ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
  11. 10
      src/google/protobuf/json/internal/unparser.cc
  12. 10
      src/google/protobuf/json/internal/writer.h
  13. 6
      src/google/protobuf/json/json.cc
  14. 10
      src/google/protobuf/json/json.h
  15. 159
      src/google/protobuf/json/json_test.cc

@ -8,6 +8,7 @@
package com.google.protobuf.util;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.BaseEncoding;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gson.Gson;
@ -29,7 +30,6 @@ import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.Descriptors.OneofDescriptor;
import com.google.protobuf.DoubleValue;
import com.google.protobuf.Duration;
import com.google.protobuf.DynamicMessage;
@ -86,30 +86,30 @@ public class JsonFormat {
return new Printer(
com.google.protobuf.TypeRegistry.getEmptyTypeRegistry(),
TypeRegistry.getEmptyTypeRegistry(),
/* alwaysOutputDefaultValueFields */ false,
/* includingDefaultValueFields */ Collections.<FieldDescriptor>emptySet(),
ShouldPrintDefaults.ONLY_IF_PRESENT,
/* includingDefaultValueFields */ ImmutableSet.of(),
/* preservingProtoFieldNames */ false,
/* omittingInsignificantWhitespace */ false,
/* printingEnumsAsInts */ false,
/* sortingMapKeys */ false);
}
/**
* A Printer converts a protobuf message to the proto3 JSON format.
*/
private enum ShouldPrintDefaults {
ONLY_IF_PRESENT, // The "normal" behavior; the others add more compared to this baseline.
ALWAYS_PRINT_EXCEPT_MESSAGES_AND_ONEOFS,
ALWAYS_PRINT_WITHOUT_PRESENCE_FIELDS,
ALWAYS_PRINT_SPECIFIED_FIELDS
}
/** A Printer converts a protobuf message to the proto3 JSON format. */
public static class Printer {
private final com.google.protobuf.TypeRegistry registry;
private final TypeRegistry oldRegistry;
// NOTE: There are 3 states for these *defaultValueFields variables:
// 1) Default - alwaysOutput is false & including is empty set. Fields only output if they are
// set to non-default values.
// 2) No-args includingDefaultValueFields() called - alwaysOutput is true & including is
// irrelevant (but set to empty set). All fields are output regardless of their values.
// 3) includingDefaultValueFields(Set<FieldDescriptor>) called - alwaysOutput is false &
// including is set to the specified set. Fields in that set are always output & fields not
// in that set are only output if set to non-default values.
private boolean alwaysOutputDefaultValueFields;
private Set<FieldDescriptor> includingDefaultValueFields;
private final ShouldPrintDefaults shouldPrintDefaults;
// Empty unless shouldPrintDefaults is set to ALWAYS_PRINT_SPECIFIED_FIELDS.
private final Set<FieldDescriptor> includingDefaultValueFields;
private final boolean preservingProtoFieldNames;
private final boolean omittingInsignificantWhitespace;
private final boolean printingEnumsAsInts;
@ -118,7 +118,7 @@ public class JsonFormat {
private Printer(
com.google.protobuf.TypeRegistry registry,
TypeRegistry oldRegistry,
boolean alwaysOutputDefaultValueFields,
ShouldPrintDefaults shouldOutputDefaults,
Set<FieldDescriptor> includingDefaultValueFields,
boolean preservingProtoFieldNames,
boolean omittingInsignificantWhitespace,
@ -126,7 +126,7 @@ public class JsonFormat {
boolean sortingMapKeys) {
this.registry = registry;
this.oldRegistry = oldRegistry;
this.alwaysOutputDefaultValueFields = alwaysOutputDefaultValueFields;
this.shouldPrintDefaults = shouldOutputDefaults;
this.includingDefaultValueFields = includingDefaultValueFields;
this.preservingProtoFieldNames = preservingProtoFieldNames;
this.omittingInsignificantWhitespace = omittingInsignificantWhitespace;
@ -148,7 +148,7 @@ public class JsonFormat {
return new Printer(
com.google.protobuf.TypeRegistry.getEmptyTypeRegistry(),
oldRegistry,
alwaysOutputDefaultValueFields,
shouldPrintDefaults,
includingDefaultValueFields,
preservingProtoFieldNames,
omittingInsignificantWhitespace,
@ -170,7 +170,7 @@ public class JsonFormat {
return new Printer(
registry,
oldRegistry,
alwaysOutputDefaultValueFields,
shouldPrintDefaults,
includingDefaultValueFields,
preservingProtoFieldNames,
omittingInsignificantWhitespace,
@ -179,18 +179,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.
*
* <p>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<FieldDescriptor> fieldsToAlwaysOutput) {
Preconditions.checkArgument(
null != fieldsToAlwaysOutput && !fieldsToAlwaysOutput.isEmpty(),
"Non-empty Set must be supplied for includingDefaultValueFields.");
if (shouldPrintDefaults != ShouldPrintDefaults.ONLY_IF_PRESENT) {
throw new IllegalStateException(
"JsonFormat includingDefaultValueFields has already been set.");
}
return new Printer(
registry,
oldRegistry,
true,
Collections.<FieldDescriptor>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<FieldDescriptor> fieldsToAlwaysOutput) {
Preconditions.checkArgument(
null != fieldsToAlwaysOutput && !fieldsToAlwaysOutput.isEmpty(),
"Non-empty Set must be supplied for includingDefaultValueFields.");
checkUnsetIncludingDefaultValueFields();
public Printer printingEnumsAsInts() {
checkUnsetPrintingEnumsAsInts();
return new Printer(
registry,
oldRegistry,
false,
Collections.unmodifiableSet(new HashSet<>(fieldsToAlwaysOutput)),
shouldPrintDefaults,
includingDefaultValueFields,
preservingProtoFieldNames,
omittingInsignificantWhitespace,
printingEnumsAsInts,
true,
sortingMapKeys);
}
private void checkUnsetIncludingDefaultValueFields() {
if (alwaysOutputDefaultValueFields || !includingDefaultValueFields.isEmpty()) {
throw new IllegalStateException(
"JsonFormat includingDefaultValueFields has already been set.");
private void checkUnsetPrintingEnumsAsInts() {
if (printingEnumsAsInts) {
throw new IllegalStateException("JsonFormat printingEnumsAsInts has already been set.");
}
}
@ -261,7 +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<FieldDescriptor> 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<FieldDescriptor> 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<FieldDescriptor, Object> fieldsToPrint = null;
if (alwaysOutputDefaultValueFields || !includingDefaultValueFields.isEmpty()) {
// message.getAllFields() will already contain all of the fields that would be
// printed normally (non-empty repeated fields, with-presence fields that are set, implicit
// presence fields that have a nonzero value). Loop over all of the fields to add any more
// fields that should be printed based on the shouldPrintDefaults setting.
Map<FieldDescriptor, Object> fieldsToPrint;
if (shouldPrintDefaults == ShouldPrintDefaults.ONLY_IF_PRESENT) {
fieldsToPrint = message.getAllFields();
} else {
fieldsToPrint = new TreeMap<FieldDescriptor, Object>(message.getAllFields());
for (FieldDescriptor field : message.getDescriptorForType().getFields()) {
if (field.isOptional()) {
if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE
&& !message.hasField(field)) {
// Always skip empty optional message fields. If not we will recurse indefinitely if
// a message has itself as a sub-field.
continue;
}
OneofDescriptor oneof = field.getContainingOneof();
if (oneof != null && !message.hasField(field)) {
// Skip all oneof fields except the one that is actually set
continue;
}
}
if (!fieldsToPrint.containsKey(field)
&& (alwaysOutputDefaultValueFields || includingDefaultValueFields.contains(field))) {
if (shouldSpeciallyPrint(field)) {
fieldsToPrint.put(field, message.getField(field));
}
}
} else {
fieldsToPrint = message.getAllFields();
}
for (Map.Entry<FieldDescriptor, Object> field : fieldsToPrint.entrySet()) {
if (printedField) {
// Add line-endings for the previous field.

@ -46,6 +46,7 @@ import com.google.protobuf.util.proto.JsonTestProto.TestRecursive;
import com.google.protobuf.util.proto.JsonTestProto.TestStruct;
import com.google.protobuf.util.proto.JsonTestProto.TestTimestamp;
import com.google.protobuf.util.proto.JsonTestProto.TestWrappers;
import com.google.protobuf.util.proto.JsonTestProto2.TestAllTypesProto2;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -1483,11 +1484,11 @@ 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(
String expectedJsonWithDefaults =
"{\n"
+ " \"optionalInt32\": 0,\n"
+ " \"optionalInt64\": \"0\",\n"
@ -1522,11 +1523,20 @@ public class JsonFormatTest {
+ " \"repeatedBytes\": [],\n"
+ " \"repeatedNestedMessage\": [],\n"
+ " \"repeatedNestedEnum\": [],\n"
+ " \"optionalAliasedEnum\": \"ALIAS_FOO\"\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<FieldDescriptor> 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<FieldDescriptor> 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.<FieldDescriptor>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

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

@ -0,0 +1,82 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// LINT: LEGACY_NAMES
syntax = "proto2";
package json_test_proto2;
option java_package = "com.google.protobuf.util.proto";
option java_outer_classname = "JsonTestProto2";
message TestAllTypesProto2 {
enum NestedEnum {
FOO = 0;
BAR = 1;
BAZ = 2;
}
enum AliasedEnum {
option allow_alias = true;
ALIAS_FOO = 0;
ALIAS_BAR = 1;
ALIAS_BAZ = 2;
QUX = 2;
qux = 2;
bAz = 2;
}
message NestedMessage {
optional int32 value = 1;
}
optional int32 optional_int32 = 1;
optional int64 optional_int64 = 2;
optional uint32 optional_uint32 = 3;
optional uint64 optional_uint64 = 4;
optional sint32 optional_sint32 = 5;
optional sint64 optional_sint64 = 6;
optional fixed32 optional_fixed32 = 7;
optional fixed64 optional_fixed64 = 8;
optional sfixed32 optional_sfixed32 = 9;
optional sfixed64 optional_sfixed64 = 10;
optional float optional_float = 11;
optional double optional_double = 12;
optional bool optional_bool = 13;
optional string optional_string = 14;
optional bytes optional_bytes = 15;
optional NestedMessage optional_nested_message = 18;
optional NestedEnum optional_nested_enum = 21;
optional AliasedEnum optional_aliased_enum = 52;
optional TestRecursive optional_recursive = 53;
// Repeated
repeated int32 repeated_int32 = 31;
repeated int64 repeated_int64 = 32;
repeated uint32 repeated_uint32 = 33;
repeated uint64 repeated_uint64 = 34;
repeated sint32 repeated_sint32 = 35;
repeated sint64 repeated_sint64 = 36;
repeated fixed32 repeated_fixed32 = 37;
repeated fixed64 repeated_fixed64 = 38;
repeated sfixed32 repeated_sfixed32 = 39;
repeated sfixed64 repeated_sfixed64 = 40;
repeated float repeated_float = 41;
repeated double repeated_double = 42;
repeated bool repeated_bool = 43;
repeated string repeated_string = 44;
repeated bytes repeated_bytes = 45;
repeated NestedMessage repeated_nested_message = 48;
repeated NestedEnum repeated_nested_enum = 51;
repeated TestRecursive repeated_recursive = 55;
}
message TestRecursive {
optional int32 value = 1;
optional TestRecursive nested = 2;
}

File diff suppressed because it is too large Load Diff

@ -1777,6 +1777,7 @@ class Proto3Test(unittest.TestCase):
# Test has presence:
for field in test_proto3_optional_pb2.TestProto3Optional.DESCRIPTOR.fields:
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:

@ -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;
}

@ -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;
}

@ -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,
_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])
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])|(?<![\ud800-\udbff])[\udc00-\udfff]')
'[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]'
)
_VALID_EXTENSION_NAME = re.compile(r'\[[a-zA-Z0-9\._]*\]$')
@ -65,68 +72,71 @@ class ParseError(Error):
def MessageToJson(
message,
including_default_value_fields=False,
preserving_proto_field_name=False,
indent=2,
sort_keys=False,
use_integers_for_enums=False,
descriptor_pool=None,
float_precision=None,
ensure_ascii=True):
ensure_ascii=True,
always_print_fields_with_no_presence=False,
):
"""Converts protobuf message to JSON format.
Args:
message: The protocol buffers message instance to serialize.
including_default_value_fields: If True, singular primitive fields,
repeated fields, and map fields will always be serialized. If
False, only serialize non-empty fields. Singular message fields
and oneof fields are not affected by this option.
preserving_proto_field_name: If True, use the original proto field
names as defined in the .proto file. If False, convert the field
names to lowerCamelCase.
indent: The JSON object will be pretty-printed with this indent level.
An indent level of 0 or negative will only insert newlines. If the
indent level is None, no newlines will be inserted.
always_print_fields_with_no_presence: If True, fields without
presence (implicit presence scalars, repeated fields, and map fields) will
always be serialized. Any field that supports presence is not affected by
this option (including singular message fields and oneof fields).
preserving_proto_field_name: If True, use the original proto field names as
defined in the .proto file. If False, convert the field names to
lowerCamelCase.
indent: The JSON object will be pretty-printed with this indent level. An
indent level of 0 or negative will only insert newlines. If the indent
level is None, no newlines will be inserted.
sort_keys: If True, then the output will be sorted by field names.
use_integers_for_enums: If true, print integers instead of enum names.
descriptor_pool: A Descriptor Pool for resolving types. If None use the
default.
float_precision: If set, use this to specify float field valid digits.
ensure_ascii: If True, strings with non-ASCII characters are escaped.
If False, Unicode strings are returned unchanged.
ensure_ascii: If True, strings with non-ASCII characters are escaped. If
False, Unicode strings are returned unchanged.
Returns:
A string containing the JSON formatted protocol buffer message.
"""
printer = _Printer(
including_default_value_fields,
preserving_proto_field_name,
use_integers_for_enums,
descriptor_pool,
float_precision=float_precision)
float_precision,
always_print_fields_with_no_presence
)
return printer.ToJsonString(message, indent, sort_keys, ensure_ascii)
def MessageToDict(
message,
including_default_value_fields=False,
always_print_fields_with_no_presence=False,
preserving_proto_field_name=False,
use_integers_for_enums=False,
descriptor_pool=None,
float_precision=None):
float_precision=None,
):
"""Converts protobuf message to a dictionary.
When the dictionary is encoded to JSON, it conforms to proto3 JSON spec.
Args:
message: The protocol buffers message instance to serialize.
including_default_value_fields: If True, singular primitive fields,
repeated fields, and map fields will always be serialized. If
False, only serialize non-empty fields. Singular message fields
and oneof fields are not affected by this option.
preserving_proto_field_name: If True, use the original proto field
names as defined in the .proto file. If False, convert the field
names to lowerCamelCase.
always_print_fields_with_no_presence: If True, fields without
presence (implicit presence scalars, repeated fields, and map fields) will
always be serialized. Any field that supports presence is not affected by
this option (including singular message fields and oneof fields).
preserving_proto_field_name: If True, use the original proto field names as
defined in the .proto file. If False, convert the field names to
lowerCamelCase.
use_integers_for_enums: If true, print integers instead of enum names.
descriptor_pool: A Descriptor Pool for resolving types. If None use the
default.
@ -136,19 +146,22 @@ def MessageToDict(
A dict representation of the protocol buffer message.
"""
printer = _Printer(
including_default_value_fields,
preserving_proto_field_name,
use_integers_for_enums,
descriptor_pool,
float_precision=float_precision)
float_precision,
always_print_fields_with_no_presence,
)
# pylint: disable=protected-access
return printer._MessageToJsonObject(message)
def _IsMapEntry(field):
return (field.type == descriptor.FieldDescriptor.TYPE_MESSAGE and
field.message_type.has_options and
field.message_type.GetOptions().map_entry)
return (
field.type == descriptor.FieldDescriptor.TYPE_MESSAGE
and field.message_type.has_options
and field.message_type.GetOptions().map_entry
)
class _Printer(object):
@ -156,12 +169,15 @@ class _Printer(object):
def __init__(
self,
including_default_value_fields=False,
preserving_proto_field_name=False,
use_integers_for_enums=False,
descriptor_pool=None,
float_precision=None):
self.including_default_value_fields = including_default_value_fields
float_precision=None,
always_print_fields_with_no_presence=False,
):
self.always_print_fields_with_no_presence = (
always_print_fields_with_no_presence
)
self.preserving_proto_field_name = preserving_proto_field_name
self.use_integers_for_enums = use_integers_for_enums
self.descriptor_pool = descriptor_pool
@ -173,7 +189,8 @@ class _Printer(object):
def ToJsonString(self, message, indent, sort_keys, ensure_ascii):
js = self._MessageToJsonObject(message)
return json.dumps(
js, indent=indent, sort_keys=sort_keys, ensure_ascii=ensure_ascii)
js, indent=indent, sort_keys=sort_keys, ensure_ascii=ensure_ascii
)
def _MessageToJsonObject(self, message):
"""Converts message to an object according to Proto3 JSON Specification."""
@ -208,13 +225,11 @@ class _Printer(object):
recorded_key = 'false'
else:
recorded_key = str(key)
js_map[recorded_key] = self._FieldToJsonObject(
v_field, value[key])
js_map[recorded_key] = self._FieldToJsonObject(v_field, value[key])
js[name] = js_map
elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED:
# Convert a repeated field.
js[name] = [self._FieldToJsonObject(field, k)
for k in value]
js[name] = [self._FieldToJsonObject(field, k) for k in value]
elif field.is_extension:
name = '[%s]' % field.full_name
js[name] = self._FieldToJsonObject(field, value)
@ -222,14 +237,20 @@ class _Printer(object):
js[name] = self._FieldToJsonObject(field, value)
# Serialize default value if including_default_value_fields is True.
if self.including_default_value_fields:
if (
self.always_print_fields_with_no_presence
):
message_descriptor = message.DESCRIPTOR
for field in message_descriptor.fields:
# Singular message fields and oneof fields will not be affected.
if ((field.label != descriptor.FieldDescriptor.LABEL_REPEATED and
field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE) or
field.containing_oneof):
# always_print_fields_with_no_presence doesn't apply to
# any field which supports presence.
if (
self.always_print_fields_with_no_presence
and field.has_presence
):
continue
if self.preserving_proto_field_name:
name = field.name
else:
@ -246,7 +267,8 @@ class _Printer(object):
except ValueError as e:
raise SerializeToJsonError(
'Failed to serialize {0} field: {1}.'.format(field.name, e)) from e
'Failed to serialize {0} field: {1}.'.format(field.name, e)
) from e
return js
@ -264,8 +286,10 @@ class _Printer(object):
return enum_value.name
else:
if field.enum_type.is_closed:
raise SerializeToJsonError('Enum field contains an integer value '
'which can not mapped to an enum value.')
raise SerializeToJsonError(
'Enum field contains an integer value '
'which can not mapped to an enum value.'
)
else:
return value
elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING:
@ -310,8 +334,9 @@ class _Printer(object):
js['value'] = self._WrapperMessageToJsonObject(sub_message)
return js
if full_name in _WKTJSONMETHODS:
js['value'] = methodcaller(_WKTJSONMETHODS[full_name][0],
sub_message)(self)
js['value'] = methodcaller(_WKTJSONMETHODS[full_name][0], sub_message)(
self
)
return js
return self._RegularMessageToJsonObject(sub_message, js)
@ -333,11 +358,15 @@ class _Printer(object):
if which == 'number_value':
value = message.number_value
if math.isinf(value):
raise ValueError('Fail to serialize Infinity for Value.number_value, '
'which would parse as string_value')
raise ValueError(
'Fail to serialize Infinity for Value.number_value, '
'which would parse as string_value'
)
if math.isnan(value):
raise ValueError('Fail to serialize NaN for Value.number_value, '
'which would parse as string_value')
raise ValueError(
'Fail to serialize NaN for Value.number_value, '
'which would parse as string_value'
)
else:
value = getattr(message, which)
oneof_descriptor = message.DESCRIPTOR.fields_by_name[which]
@ -345,8 +374,7 @@ class _Printer(object):
def _ListValueMessageToJsonObject(self, message):
"""Converts ListValue message according to Proto3 JSON Specification."""
return [self._ValueMessageToJsonObject(value)
for value in message.values]
return [self._ValueMessageToJsonObject(value) for value in message.values]
def _StructMessageToJsonObject(self, message):
"""Converts Struct message according to Proto3 JSON Specification."""
@ -358,7 +386,8 @@ class _Printer(object):
def _WrapperMessageToJsonObject(self, message):
return self._FieldToJsonObject(
message.DESCRIPTOR.fields_by_name['value'], message.value)
message.DESCRIPTOR.fields_by_name['value'], message.value
)
def _IsWrapperMessage(message_descriptor):
@ -389,11 +418,13 @@ def _CreateMessageFromTypeUrl(type_url, descriptor_pool):
return message_class()
def Parse(text,
def Parse(
text,
message,
ignore_unknown_fields=False,
descriptor_pool=None,
max_recursion_depth=100):
max_recursion_depth=100,
):
"""Parses a JSON representation of a protocol message into a message.
Args:
@ -402,9 +433,9 @@ def Parse(text,
ignore_unknown_fields: If True, do not raise errors for unknown fields.
descriptor_pool: A Descriptor Pool for resolving types. If None use the
default.
max_recursion_depth: max recursion depth of JSON message to be
deserialized. JSON messages over this depth will fail to be
deserialized. Default value is 100.
max_recursion_depth: max recursion depth of JSON message to be deserialized.
JSON messages over this depth will fail to be deserialized. Default value
is 100.
Returns:
The same message passed as argument.
@ -418,15 +449,18 @@ def Parse(text,
js = json.loads(text, object_pairs_hook=_DuplicateChecker)
except ValueError as e:
raise ParseError('Failed to load JSON: {0}.'.format(str(e))) from e
return ParseDict(js, message, ignore_unknown_fields, descriptor_pool,
max_recursion_depth)
return ParseDict(
js, message, ignore_unknown_fields, descriptor_pool, max_recursion_depth
)
def ParseDict(js_dict,
def ParseDict(
js_dict,
message,
ignore_unknown_fields=False,
descriptor_pool=None,
max_recursion_depth=100):
max_recursion_depth=100,
):
"""Parses a JSON dictionary representation into a message.
Args:
@ -435,9 +469,9 @@ def ParseDict(js_dict,
ignore_unknown_fields: If True, do not raise errors for unknown fields.
descriptor_pool: A Descriptor Pool for resolving types. If None use the
default.
max_recursion_depth: max recursion depth of JSON message to be
deserialized. JSON messages over this depth will fail to be
deserialized. Default value is 100.
max_recursion_depth: max recursion depth of JSON message to be deserialized.
JSON messages over this depth will fail to be deserialized. Default value
is 100.
Returns:
The same message passed as argument.
@ -453,8 +487,9 @@ _INT_OR_FLOAT = (int, float)
class _Parser(object):
"""JSON format parser for protocol message."""
def __init__(self, ignore_unknown_fields, descriptor_pool,
max_recursion_depth):
def __init__(
self, ignore_unknown_fields, descriptor_pool, max_recursion_depth
):
self.ignore_unknown_fields = ignore_unknown_fields
self.descriptor_pool = descriptor_pool
self.max_recursion_depth = max_recursion_depth
@ -473,8 +508,11 @@ class _Parser(object):
"""
self.recursion_depth += 1
if self.recursion_depth > 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 '
raise ParseError(
'Message type "{0}" should not have multiple '
'"{1}" fields at "{2}".'.format(
message.DESCRIPTOR.full_name, name, path))
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 '
raise ParseError(
'Message type "{0}" should not have multiple '
'"{1}" oneof fields at "{2}".'.format(
message.DESCRIPTOR.full_name, oneof_name,
path))
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'
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))
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'
raise ParseError(
'null is not allowed to be used as an element'
' in a repeated field at {0}.{1}[{2}]'.format(
path, name, index))
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,12 +673,16 @@ 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(
@ -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],
self.ConvertMessage(
value[key],
getattr(message, field.name)[key_value],
'{0}[{1}]'.format(path, 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',
],
}

@ -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()) {

@ -433,8 +433,6 @@ absl::Status WriteField(JsonWriter& writer, const Msg<Traits>& msg,
} else if (Traits::IsRepeated(field)) {
return WriteRepeated<Traits>(writer, msg, field);
} else if (Traits::GetSize(field, msg) == 0) {
// We can only get here if always_print_primitive_fields is true.
ABSL_DCHECK(writer.options().always_print_primitive_fields);
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<Traits>& msg,
Field<Traits> 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) {

@ -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;

@ -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.

@ -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;

@ -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<protobuf_unittest::TestDefaultEnumValue>(*printed);
ASSERT_OK(parsed);
EXPECT_EQ(parsed->enum_value(), protobuf_unittest::DEFAULT);
}
TEST_P(JsonTest, QuotedEnumValue) {
auto m = ToProto<TestEnumValue>(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) {

Loading…
Cancel
Save