aboutsummaryrefslogtreecommitdiffhomepage
path: root/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/util/src/main/java/com/google/protobuf/util/JsonFormat.java')
-rw-r--r--java/util/src/main/java/com/google/protobuf/util/JsonFormat.java175
1 files changed, 98 insertions, 77 deletions
diff --git a/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java b/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java
index 838700f7..5e0f5fe3 100644
--- a/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java
+++ b/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java
@@ -30,6 +30,7 @@
package com.google.protobuf.util;
+import com.google.common.base.Preconditions;
import com.google.common.io.BaseEncoding;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -102,7 +103,8 @@ public class JsonFormat {
* Creates a {@link Printer} with default configurations.
*/
public static Printer printer() {
- return new Printer(TypeRegistry.getEmptyTypeRegistry(), false, false, false);
+ return new Printer(
+ TypeRegistry.getEmptyTypeRegistry(), false, Collections.emptySet(), false, false);
}
/**
@@ -110,16 +112,27 @@ public class JsonFormat {
*/
public static class Printer {
private final TypeRegistry registry;
- private final boolean includingDefaultValueFields;
+ // 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 boolean preservingProtoFieldNames;
private final boolean omittingInsignificantWhitespace;
private Printer(
TypeRegistry registry,
- boolean includingDefaultValueFields,
+ boolean alwaysOutputDefaultValueFields,
+ Set<FieldDescriptor> includingDefaultValueFields,
boolean preservingProtoFieldNames,
boolean omittingInsignificantWhitespace) {
this.registry = registry;
+ this.alwaysOutputDefaultValueFields = alwaysOutputDefaultValueFields;
this.includingDefaultValueFields = includingDefaultValueFields;
this.preservingProtoFieldNames = preservingProtoFieldNames;
this.omittingInsignificantWhitespace = omittingInsignificantWhitespace;
@@ -137,6 +150,7 @@ public class JsonFormat {
}
return new Printer(
registry,
+ alwaysOutputDefaultValueFields,
includingDefaultValueFields,
preservingProtoFieldNames,
omittingInsignificantWhitespace);
@@ -149,8 +163,41 @@ public class JsonFormat {
* {@link Printer}.
*/
public Printer includingDefaultValueFields() {
+ checkUnsetIncludingDefaultValueFields();
return new Printer(
- registry, true, preservingProtoFieldNames, omittingInsignificantWhitespace);
+ registry,
+ true,
+ Collections.emptySet(),
+ preservingProtoFieldNames,
+ omittingInsignificantWhitespace);
+ }
+
+ /**
+ * Creates a new {@link Printer} that will also print default-valued fields if their
+ * FieldDescriptors are found in the supplied set. Empty repeated fields and map fields will be
+ * printed as well, if they match. The new Printer clones all other configurations from the
+ * current {@link Printer}. Call includingDefaultValueFields() with no args to unconditionally
+ * output all fields.
+ */
+ public Printer includingDefaultValueFields(Set<FieldDescriptor> fieldsToAlwaysOutput) {
+ Preconditions.checkArgument(
+ null != fieldsToAlwaysOutput && !fieldsToAlwaysOutput.isEmpty(),
+ "Non-empty Set must be supplied for includingDefaultValueFields.");
+
+ checkUnsetIncludingDefaultValueFields();
+ return new Printer(
+ registry,
+ false,
+ fieldsToAlwaysOutput,
+ preservingProtoFieldNames,
+ omittingInsignificantWhitespace);
+ }
+
+ private void checkUnsetIncludingDefaultValueFields() {
+ if (alwaysOutputDefaultValueFields || !includingDefaultValueFields.isEmpty()) {
+ throw new IllegalStateException(
+ "JsonFormat includingDefaultValueFields has already been set.");
+ }
}
/**
@@ -161,15 +208,20 @@ public class JsonFormat {
*/
public Printer preservingProtoFieldNames() {
return new Printer(
- registry, includingDefaultValueFields, true, omittingInsignificantWhitespace);
+ registry,
+ alwaysOutputDefaultValueFields,
+ includingDefaultValueFields,
+ true,
+ omittingInsignificantWhitespace);
}
/**
- * Create a new {@link Printer} that will omit all insignificant whitespace
- * in the JSON output. This new Printer clones all other configurations from the
- * current Printer. Insignificant whitespace is defined by the JSON spec as whitespace
- * that appear between JSON structural elements:
+ * Create a new {@link Printer} that will omit all insignificant whitespace in the JSON output.
+ * This new Printer clones all other configurations from the current Printer. Insignificant
+ * whitespace is defined by the JSON spec as whitespace that appear between JSON structural
+ * elements:
+ *
* <pre>
* ws = *(
* %x20 / ; Space
@@ -177,18 +229,24 @@ public class JsonFormat {
* %x0A / ; Line feed or New line
* %x0D ) ; Carriage return
* </pre>
+ *
* See <a href="https://tools.ietf.org/html/rfc7159">https://tools.ietf.org/html/rfc7159</a>
* current {@link Printer}.
*/
public Printer omittingInsignificantWhitespace() {
- return new Printer(registry, includingDefaultValueFields, preservingProtoFieldNames, true);
+ return new Printer(
+ registry,
+ alwaysOutputDefaultValueFields,
+ includingDefaultValueFields,
+ preservingProtoFieldNames,
+ true);
}
/**
* Converts a protobuf message to JSON format.
*
- * @throws InvalidProtocolBufferException if the message contains Any types
- * that can't be resolved.
+ * @throws InvalidProtocolBufferException if the message contains Any types that can't be
+ * resolved.
* @throws IOException if writing to the output fails.
*/
public void appendTo(MessageOrBuilder message, Appendable output) throws IOException {
@@ -196,6 +254,7 @@ public class JsonFormat {
// mobile.
new PrinterImpl(
registry,
+ alwaysOutputDefaultValueFields,
includingDefaultValueFields,
preservingProtoFieldNames,
output,
@@ -428,19 +487,16 @@ public class JsonFormat {
this.output = output;
}
- /**
- * ignored by compact printer
- */
+ /** ignored by compact printer */
+ @Override
public void indent() {}
- /**
- * ignored by compact printer
- */
+ /** ignored by compact printer */
+ @Override
public void outdent() {}
- /**
- * Print text to the output stream.
- */
+ /** Print text to the output stream. */
+ @Override
public void print(final CharSequence text) throws IOException {
output.append(text);
}
@@ -458,18 +514,17 @@ public class JsonFormat {
}
/**
- * Indent text by two spaces. After calling Indent(), two spaces will be
- * inserted at the beginning of each line of text. Indent() may be called
- * multiple times to produce deeper indents.
+ * Indent text by two spaces. After calling Indent(), two spaces will be inserted at the
+ * beginning of each line of text. Indent() may be called multiple times to produce deeper
+ * indents.
*/
+ @Override
public void indent() {
indent.append(" ");
}
- /**
- * Reduces the current indent level by two spaces, or crashes if the indent
- * level is zero.
- */
+ /** Reduces the current indent level by two spaces, or crashes if the indent level is zero. */
+ @Override
public void outdent() {
final int length = indent.length();
if (length < 2) {
@@ -478,9 +533,8 @@ public class JsonFormat {
indent.delete(length - 2, length);
}
- /**
- * Print text to the output stream.
- */
+ /** Print text to the output stream. */
+ @Override
public void print(final CharSequence text) throws IOException {
final int size = text.length();
int pos = 0;
@@ -512,7 +566,8 @@ public class JsonFormat {
*/
private static final class PrinterImpl {
private final TypeRegistry registry;
- private final boolean includingDefaultValueFields;
+ private final boolean alwaysOutputDefaultValueFields;
+ private final Set<FieldDescriptor> includingDefaultValueFields;
private final boolean preservingProtoFieldNames;
private final TextGenerator generator;
// We use Gson to help handle string escapes.
@@ -526,11 +581,13 @@ public class JsonFormat {
PrinterImpl(
TypeRegistry registry,
- boolean includingDefaultValueFields,
+ boolean alwaysOutputDefaultValueFields,
+ Set<FieldDescriptor> includingDefaultValueFields,
boolean preservingProtoFieldNames,
Appendable jsonOutput,
boolean omittingInsignificantWhitespace) {
this.registry = registry;
+ this.alwaysOutputDefaultValueFields = alwaysOutputDefaultValueFields;
this.includingDefaultValueFields = includingDefaultValueFields;
this.preservingProtoFieldNames = preservingProtoFieldNames;
this.gson = GsonHolder.DEFAULT_GSON;
@@ -781,23 +838,26 @@ public class JsonFormat {
printedField = true;
}
Map<FieldDescriptor, Object> fieldsToPrint = null;
- if (includingDefaultValueFields) {
- fieldsToPrint = new TreeMap<FieldDescriptor, Object>();
+ if (alwaysOutputDefaultValueFields || !includingDefaultValueFields.isEmpty()) {
+ 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)){
+ && !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
+ // Skip all oneof fields except the one that is actually set
continue;
}
}
- fieldsToPrint.put(field, message.getField(field));
+ if (!fieldsToPrint.containsKey(field)
+ && (alwaysOutputDefaultValueFields || includingDefaultValueFields.contains(field))) {
+ fieldsToPrint.put(field, message.getField(field));
+ }
}
} else {
fieldsToPrint = message.getAllFields();
@@ -1451,45 +1511,6 @@ public class JsonFormat {
}
}
- /**
- * Gets the default value for a field type. Note that we use proto3
- * language defaults and ignore any default values set through the
- * proto "default" option.
- */
- private Object getDefaultValue(FieldDescriptor field, Message.Builder builder) {
- switch (field.getType()) {
- case INT32:
- case SINT32:
- case SFIXED32:
- case UINT32:
- case FIXED32:
- return 0;
- case INT64:
- case SINT64:
- case SFIXED64:
- case UINT64:
- case FIXED64:
- return 0L;
- case FLOAT:
- return 0.0f;
- case DOUBLE:
- return 0.0;
- case BOOL:
- return false;
- case STRING:
- return "";
- case BYTES:
- return ByteString.EMPTY;
- case ENUM:
- return field.getEnumType().getValues().get(0);
- case MESSAGE:
- case GROUP:
- return builder.newBuilderForField(field).getDefaultInstanceForType();
- default:
- throw new IllegalStateException("Invalid field type: " + field.getType());
- }
- }
-
private void mergeRepeatedField(
FieldDescriptor field, JsonElement json, Message.Builder builder)
throws InvalidProtocolBufferException {