88package com .google .protobuf .util ;
99
1010import com .google .common .base .Preconditions ;
11+ import com .google .common .collect .ImmutableSet ;
1112import com .google .common .io .BaseEncoding ;
1213import com .google .errorprone .annotations .CanIgnoreReturnValue ;
1314import com .google .gson .Gson ;
2930import com .google .protobuf .Descriptors .EnumValueDescriptor ;
3031import com .google .protobuf .Descriptors .FieldDescriptor ;
3132import com .google .protobuf .Descriptors .FileDescriptor ;
32- import com .google .protobuf .Descriptors .OneofDescriptor ;
3333import com .google .protobuf .DoubleValue ;
3434import com .google .protobuf .Duration ;
3535import 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