Skip to content

Commit 043191b

Browse files
protobuf-github-botzhangskz
authored andcommitted
Add a new 'includingDefaultValueWithoutPresenceFields' option to the Java parser which is intended to replace the current 'includingDefaultValueFields'.
The old flag accidentally had inconsistent behavior between proto2 optional and proto3 optional fields, the new flag treats them consistently (and is consistent with the preexisting behavior of the Go JSON serializer). includingDefaultValueFields is now deprecated and will be removed in an upcoming release. PiperOrigin-RevId: 603449195
1 parent 1f3bf1d commit 043191b

File tree

4 files changed

+403
-119
lines changed

4 files changed

+403
-119
lines changed

java/util/src/main/java/com/google/protobuf/util/JsonFormat.java

Lines changed: 124 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package com.google.protobuf.util;
99

1010
import com.google.common.base.Preconditions;
11+
import com.google.common.collect.ImmutableSet;
1112
import com.google.common.io.BaseEncoding;
1213
import com.google.errorprone.annotations.CanIgnoreReturnValue;
1314
import com.google.gson.Gson;
@@ -29,7 +30,6 @@
2930
import com.google.protobuf.Descriptors.EnumValueDescriptor;
3031
import com.google.protobuf.Descriptors.FieldDescriptor;
3132
import com.google.protobuf.Descriptors.FileDescriptor;
32-
import com.google.protobuf.Descriptors.OneofDescriptor;
3333
import com.google.protobuf.DoubleValue;
3434
import com.google.protobuf.Duration;
3535
import com.google.protobuf.DynamicMessage;
@@ -86,30 +86,30 @@ public static Printer printer() {
8686
return new Printer(
8787
com.google.protobuf.TypeRegistry.getEmptyTypeRegistry(),
8888
TypeRegistry.getEmptyTypeRegistry(),
89-
/* alwaysOutputDefaultValueFields */ false,
90-
/* includingDefaultValueFields */ Collections.<FieldDescriptor>emptySet(),
89+
ShouldPrintDefaults.ONLY_IF_PRESENT,
90+
/* includingDefaultValueFields */ ImmutableSet.of(),
9191
/* preservingProtoFieldNames */ false,
9292
/* omittingInsignificantWhitespace */ false,
9393
/* printingEnumsAsInts */ false,
9494
/* sortingMapKeys */ false);
9595
}
9696

97-
/**
98-
* A Printer converts a protobuf message to the proto3 JSON format.
99-
*/
97+
private enum ShouldPrintDefaults {
98+
ONLY_IF_PRESENT, // The "normal" behavior; the others add more compared to this baseline.
99+
ALWAYS_PRINT_EXCEPT_MESSAGES_AND_ONEOFS,
100+
ALWAYS_PRINT_WITHOUT_PRESENCE_FIELDS,
101+
ALWAYS_PRINT_SPECIFIED_FIELDS
102+
}
103+
104+
/** A Printer converts a protobuf message to the proto3 JSON format. */
100105
public static class Printer {
101106
private final com.google.protobuf.TypeRegistry registry;
102107
private final TypeRegistry oldRegistry;
103-
// NOTE: There are 3 states for these *defaultValueFields variables:
104-
// 1) Default - alwaysOutput is false & including is empty set. Fields only output if they are
105-
// set to non-default values.
106-
// 2) No-args includingDefaultValueFields() called - alwaysOutput is true & including is
107-
// irrelevant (but set to empty set). All fields are output regardless of their values.
108-
// 3) includingDefaultValueFields(Set<FieldDescriptor>) called - alwaysOutput is false &
109-
// including is set to the specified set. Fields in that set are always output & fields not
110-
// in that set are only output if set to non-default values.
111-
private boolean alwaysOutputDefaultValueFields;
112-
private Set<FieldDescriptor> includingDefaultValueFields;
108+
private final ShouldPrintDefaults shouldPrintDefaults;
109+
110+
// Empty unless shouldPrintDefaults is set to ALWAYS_PRINT_SPECIFIED_FIELDS.
111+
private final Set<FieldDescriptor> includingDefaultValueFields;
112+
113113
private final boolean preservingProtoFieldNames;
114114
private final boolean omittingInsignificantWhitespace;
115115
private final boolean printingEnumsAsInts;
@@ -118,15 +118,15 @@ public static class Printer {
118118
private Printer(
119119
com.google.protobuf.TypeRegistry registry,
120120
TypeRegistry oldRegistry,
121-
boolean alwaysOutputDefaultValueFields,
121+
ShouldPrintDefaults shouldOutputDefaults,
122122
Set<FieldDescriptor> includingDefaultValueFields,
123123
boolean preservingProtoFieldNames,
124124
boolean omittingInsignificantWhitespace,
125125
boolean printingEnumsAsInts,
126126
boolean sortingMapKeys) {
127127
this.registry = registry;
128128
this.oldRegistry = oldRegistry;
129-
this.alwaysOutputDefaultValueFields = alwaysOutputDefaultValueFields;
129+
this.shouldPrintDefaults = shouldOutputDefaults;
130130
this.includingDefaultValueFields = includingDefaultValueFields;
131131
this.preservingProtoFieldNames = preservingProtoFieldNames;
132132
this.omittingInsignificantWhitespace = omittingInsignificantWhitespace;
@@ -148,7 +148,7 @@ public Printer usingTypeRegistry(TypeRegistry oldRegistry) {
148148
return new Printer(
149149
com.google.protobuf.TypeRegistry.getEmptyTypeRegistry(),
150150
oldRegistry,
151-
alwaysOutputDefaultValueFields,
151+
shouldPrintDefaults,
152152
includingDefaultValueFields,
153153
preservingProtoFieldNames,
154154
omittingInsignificantWhitespace,
@@ -170,7 +170,7 @@ public Printer usingTypeRegistry(com.google.protobuf.TypeRegistry registry) {
170170
return new Printer(
171171
registry,
172172
oldRegistry,
173-
alwaysOutputDefaultValueFields,
173+
shouldPrintDefaults,
174174
includingDefaultValueFields,
175175
preservingProtoFieldNames,
176176
omittingInsignificantWhitespace,
@@ -179,75 +179,104 @@ public Printer usingTypeRegistry(com.google.protobuf.TypeRegistry registry) {
179179
}
180180

181181
/**
182-
* Creates a new {@link Printer} that will also print fields set to their
183-
* defaults. Empty repeated fields and map fields will be printed as well.
184-
* The new Printer clones all other configurations from the current
185-
* {@link Printer}.
182+
* Creates a new {@link Printer} that will always print fields unless they are a message type or
183+
* in a oneof.
184+
*
185+
* <p>Note that this does print Proto2 Optional but does not print Proto3 Optional fields, as
186+
* the latter is represented using a synthetic oneof.
187+
*
188+
* <p>The new Printer clones all other configurations from the current {@link Printer}.
189+
*
190+
* @deprecated Prefer {@link #includingDefaultValueWithoutPresenceFields}
186191
*/
192+
@Deprecated
187193
public Printer includingDefaultValueFields() {
188-
checkUnsetIncludingDefaultValueFields();
194+
if (shouldPrintDefaults != ShouldPrintDefaults.ONLY_IF_PRESENT) {
195+
throw new IllegalStateException(
196+
"JsonFormat includingDefaultValueFields has already been set.");
197+
}
189198
return new Printer(
190199
registry,
191200
oldRegistry,
192-
true,
193-
Collections.<FieldDescriptor>emptySet(),
201+
ShouldPrintDefaults.ALWAYS_PRINT_EXCEPT_MESSAGES_AND_ONEOFS,
202+
ImmutableSet.of(),
194203
preservingProtoFieldNames,
195204
omittingInsignificantWhitespace,
196205
printingEnumsAsInts,
197206
sortingMapKeys);
198207
}
199208

200209
/**
201-
* Creates a new {@link Printer} that prints enum field values as integers instead of as
202-
* string. The new Printer clones all other configurations from the current {@link Printer}.
210+
* Creates a new {@link Printer} that will also print default-valued fields if their
211+
* FieldDescriptors are found in the supplied set. Empty repeated fields and map fields will be
212+
* printed as well, if they match. The new Printer clones all other configurations from the
213+
* current {@link Printer}. Call includingDefaultValueFields() with no args to unconditionally
214+
* output all fields.
215+
*
216+
* <p>Note that non-repeated message fields or fields in a oneof are not honored if provided
217+
* here.
203218
*/
204-
public Printer printingEnumsAsInts() {
205-
checkUnsetPrintingEnumsAsInts();
219+
public Printer includingDefaultValueFields(Set<FieldDescriptor> fieldsToAlwaysOutput) {
220+
Preconditions.checkArgument(
221+
null != fieldsToAlwaysOutput && !fieldsToAlwaysOutput.isEmpty(),
222+
"Non-empty Set must be supplied for includingDefaultValueFields.");
223+
if (shouldPrintDefaults != ShouldPrintDefaults.ONLY_IF_PRESENT) {
224+
throw new IllegalStateException(
225+
"JsonFormat includingDefaultValueFields has already been set.");
226+
}
206227
return new Printer(
207228
registry,
208229
oldRegistry,
209-
alwaysOutputDefaultValueFields,
210-
includingDefaultValueFields,
230+
ShouldPrintDefaults.ALWAYS_PRINT_SPECIFIED_FIELDS,
231+
ImmutableSet.copyOf(fieldsToAlwaysOutput),
211232
preservingProtoFieldNames,
212233
omittingInsignificantWhitespace,
213-
true,
234+
printingEnumsAsInts,
214235
sortingMapKeys);
215236
}
216237

217-
private void checkUnsetPrintingEnumsAsInts() {
218-
if (printingEnumsAsInts) {
219-
throw new IllegalStateException("JsonFormat printingEnumsAsInts has already been set.");
238+
/**
239+
* Creates a new {@link Printer} that will print any field that does not support presence even
240+
* if it would not otherwise be printed (empty repeated fields, empty map fields, and implicit
241+
* presence scalars set to their default value). The new Printer clones all other configurations
242+
* from the current {@link Printer}.
243+
*/
244+
public Printer includingDefaultValueWithoutPresenceFields() {
245+
if (shouldPrintDefaults != ShouldPrintDefaults.ONLY_IF_PRESENT) {
246+
throw new IllegalStateException(
247+
"JsonFormat includingDefaultValueFields has already been set.");
220248
}
249+
return new Printer(
250+
registry,
251+
oldRegistry,
252+
ShouldPrintDefaults.ALWAYS_PRINT_WITHOUT_PRESENCE_FIELDS,
253+
ImmutableSet.of(),
254+
preservingProtoFieldNames,
255+
omittingInsignificantWhitespace,
256+
printingEnumsAsInts,
257+
sortingMapKeys);
221258
}
222259

223260
/**
224-
* Creates a new {@link Printer} that will also print default-valued fields if their
225-
* FieldDescriptors are found in the supplied set. Empty repeated fields and map fields will be
226-
* printed as well, if they match. The new Printer clones all other configurations from the
227-
* current {@link Printer}. Call includingDefaultValueFields() with no args to unconditionally
228-
* output all fields.
261+
* Creates a new {@link Printer} that prints enum field values as integers instead of as string.
262+
* The new Printer clones all other configurations from the current {@link Printer}.
229263
*/
230-
public Printer includingDefaultValueFields(Set<FieldDescriptor> fieldsToAlwaysOutput) {
231-
Preconditions.checkArgument(
232-
null != fieldsToAlwaysOutput && !fieldsToAlwaysOutput.isEmpty(),
233-
"Non-empty Set must be supplied for includingDefaultValueFields.");
234-
235-
checkUnsetIncludingDefaultValueFields();
264+
public Printer printingEnumsAsInts() {
265+
checkUnsetPrintingEnumsAsInts();
236266
return new Printer(
237267
registry,
238268
oldRegistry,
239-
false,
240-
Collections.unmodifiableSet(new HashSet<>(fieldsToAlwaysOutput)),
269+
shouldPrintDefaults,
270+
includingDefaultValueFields,
241271
preservingProtoFieldNames,
242272
omittingInsignificantWhitespace,
243-
printingEnumsAsInts,
273+
true,
244274
sortingMapKeys);
245275
}
246276

247-
private void checkUnsetIncludingDefaultValueFields() {
248-
if (alwaysOutputDefaultValueFields || !includingDefaultValueFields.isEmpty()) {
249-
throw new IllegalStateException(
250-
"JsonFormat includingDefaultValueFields has already been set.");
277+
private void checkUnsetPrintingEnumsAsInts() {
278+
if (printingEnumsAsInts) {
279+
throw new IllegalStateException("JsonFormat printingEnumsAsInts has already been set.");
251280
}
252281
}
253282

@@ -261,7 +290,7 @@ public Printer preservingProtoFieldNames() {
261290
return new Printer(
262291
registry,
263292
oldRegistry,
264-
alwaysOutputDefaultValueFields,
293+
shouldPrintDefaults,
265294
includingDefaultValueFields,
266295
true,
267296
omittingInsignificantWhitespace,
@@ -290,7 +319,7 @@ public Printer omittingInsignificantWhitespace() {
290319
return new Printer(
291320
registry,
292321
oldRegistry,
293-
alwaysOutputDefaultValueFields,
322+
shouldPrintDefaults,
294323
includingDefaultValueFields,
295324
preservingProtoFieldNames,
296325
true,
@@ -313,7 +342,7 @@ public Printer sortingMapKeys() {
313342
return new Printer(
314343
registry,
315344
oldRegistry,
316-
alwaysOutputDefaultValueFields,
345+
shouldPrintDefaults,
317346
includingDefaultValueFields,
318347
preservingProtoFieldNames,
319348
omittingInsignificantWhitespace,
@@ -334,7 +363,7 @@ public void appendTo(MessageOrBuilder message, Appendable output) throws IOExcep
334363
new PrinterImpl(
335364
registry,
336365
oldRegistry,
337-
alwaysOutputDefaultValueFields,
366+
shouldPrintDefaults,
338367
includingDefaultValueFields,
339368
preservingProtoFieldNames,
340369
output,
@@ -685,7 +714,7 @@ private void write(final CharSequence data) throws IOException {
685714
private static final class PrinterImpl {
686715
private final com.google.protobuf.TypeRegistry registry;
687716
private final TypeRegistry oldRegistry;
688-
private final boolean alwaysOutputDefaultValueFields;
717+
private final ShouldPrintDefaults shouldPrintDefaults;
689718
private final Set<FieldDescriptor> includingDefaultValueFields;
690719
private final boolean preservingProtoFieldNames;
691720
private final boolean printingEnumsAsInts;
@@ -703,7 +732,7 @@ private static class GsonHolder {
703732
PrinterImpl(
704733
com.google.protobuf.TypeRegistry registry,
705734
TypeRegistry oldRegistry,
706-
boolean alwaysOutputDefaultValueFields,
735+
ShouldPrintDefaults shouldPrintDefaults,
707736
Set<FieldDescriptor> includingDefaultValueFields,
708737
boolean preservingProtoFieldNames,
709738
Appendable jsonOutput,
@@ -712,7 +741,7 @@ private static class GsonHolder {
712741
boolean sortingMapKeys) {
713742
this.registry = registry;
714743
this.oldRegistry = oldRegistry;
715-
this.alwaysOutputDefaultValueFields = alwaysOutputDefaultValueFields;
744+
this.shouldPrintDefaults = shouldPrintDefaults;
716745
this.includingDefaultValueFields = includingDefaultValueFields;
717746
this.preservingProtoFieldNames = preservingProtoFieldNames;
718747
this.printingEnumsAsInts = printingEnumsAsInts;
@@ -965,6 +994,28 @@ private void printListValue(MessageOrBuilder message) throws IOException {
965994
printRepeatedFieldValue(field, message.getField(field));
966995
}
967996

997+
// Whether a set option means the corresponding field should be printed even if it normally
998+
// wouldn't be.
999+
private boolean shouldSpeciallyPrint(FieldDescriptor field) {
1000+
switch (shouldPrintDefaults) {
1001+
case ONLY_IF_PRESENT:
1002+
return false;
1003+
case ALWAYS_PRINT_EXCEPT_MESSAGES_AND_ONEOFS:
1004+
return !field.hasPresence()
1005+
|| (field.getJavaType() != FieldDescriptor.JavaType.MESSAGE
1006+
&& field.getContainingOneof() == null);
1007+
case ALWAYS_PRINT_WITHOUT_PRESENCE_FIELDS:
1008+
return !field.hasPresence();
1009+
case ALWAYS_PRINT_SPECIFIED_FIELDS:
1010+
// For legacy code compatibility, we don't honor non-repeated message or oneof fields even
1011+
// if they're explicitly requested. :(
1012+
return !(field.getJavaType() == FieldDescriptor.JavaType.MESSAGE && !field.isRepeated())
1013+
&& field.getContainingOneof() == null
1014+
&& includingDefaultValueFields.contains(field);
1015+
}
1016+
throw new AssertionError("Unknown shouldPrintDefaults: " + shouldPrintDefaults);
1017+
}
1018+
9681019
/** Prints a regular message with an optional type URL. */
9691020
private void print(MessageOrBuilder message, @Nullable String typeUrl) throws IOException {
9701021
generator.print("{" + blankOrNewLine);
@@ -975,31 +1026,23 @@ private void print(MessageOrBuilder message, @Nullable String typeUrl) throws IO
9751026
generator.print("\"@type\":" + blankOrSpace + gson.toJson(typeUrl));
9761027
printedField = true;
9771028
}
978-
Map<FieldDescriptor, Object> fieldsToPrint = null;
979-
if (alwaysOutputDefaultValueFields || !includingDefaultValueFields.isEmpty()) {
1029+
1030+
// message.getAllFields() will already contain all of the fields that would be
1031+
// printed normally (non-empty repeated fields, with-presence fields that are set, implicit
1032+
// presence fields that have a nonzero value). Loop over all of the fields to add any more
1033+
// fields that should be printed based on the shouldPrintDefaults setting.
1034+
Map<FieldDescriptor, Object> fieldsToPrint;
1035+
if (shouldPrintDefaults == ShouldPrintDefaults.ONLY_IF_PRESENT) {
1036+
fieldsToPrint = message.getAllFields();
1037+
} else {
9801038
fieldsToPrint = new TreeMap<FieldDescriptor, Object>(message.getAllFields());
9811039
for (FieldDescriptor field : message.getDescriptorForType().getFields()) {
982-
if (field.isOptional()) {
983-
if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE
984-
&& !message.hasField(field)) {
985-
// Always skip empty optional message fields. If not we will recurse indefinitely if
986-
// a message has itself as a sub-field.
987-
continue;
988-
}
989-
OneofDescriptor oneof = field.getContainingOneof();
990-
if (oneof != null && !message.hasField(field)) {
991-
// Skip all oneof fields except the one that is actually set
992-
continue;
993-
}
994-
}
995-
if (!fieldsToPrint.containsKey(field)
996-
&& (alwaysOutputDefaultValueFields || includingDefaultValueFields.contains(field))) {
1040+
if (shouldSpeciallyPrint(field)) {
9971041
fieldsToPrint.put(field, message.getField(field));
9981042
}
9991043
}
1000-
} else {
1001-
fieldsToPrint = message.getAllFields();
10021044
}
1045+
10031046
for (Map.Entry<FieldDescriptor, Object> field : fieldsToPrint.entrySet()) {
10041047
if (printedField) {
10051048
// Add line-endings for the previous field.

0 commit comments

Comments
 (0)