aboutsummaryrefslogtreecommitdiffhomepage
path: root/java/util
diff options
context:
space:
mode:
Diffstat (limited to 'java/util')
-rw-r--r--java/util/src/main/java/com/google/protobuf/util/JsonFormat.java175
-rw-r--r--java/util/src/main/java/com/google/protobuf/util/Timestamps.java2
-rw-r--r--java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java137
3 files changed, 225 insertions, 89 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 {
diff --git a/java/util/src/main/java/com/google/protobuf/util/Timestamps.java b/java/util/src/main/java/com/google/protobuf/util/Timestamps.java
index 1d631a2c..d0bac417 100644
--- a/java/util/src/main/java/com/google/protobuf/util/Timestamps.java
+++ b/java/util/src/main/java/com/google/protobuf/util/Timestamps.java
@@ -297,7 +297,7 @@ public final class Timestamps {
* Convert a Timestamp to the number of microseconds elapsed from the epoch.
*
* <p>The result will be rounded down to the nearest microsecond. E.g., if the timestamp
- * represents "1969-12-31T23:59:59.999999999Z", it will be rounded to -1 millisecond.
+ * represents "1969-12-31T23:59:59.999999999Z", it will be rounded to -1 microsecond.
*/
public static long toMicros(Timestamp timestamp) {
checkValid(timestamp);
diff --git a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java
index de02c117..6943093f 100644
--- a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java
+++ b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java
@@ -34,6 +34,7 @@ import com.google.protobuf.Any;
import com.google.protobuf.BoolValue;
import com.google.protobuf.ByteString;
import com.google.protobuf.BytesValue;
+import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.DoubleValue;
import com.google.protobuf.FloatValue;
import com.google.protobuf.Int32Value;
@@ -68,9 +69,12 @@ import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.math.BigInteger;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import junit.framework.TestCase;
public class JsonFormatTest extends TestCase {
@@ -284,8 +288,8 @@ public class JsonFormatTest extends TestCase {
assertEquals(9012, message.getOptionalSint32());
assertEquals(3456, message.getOptionalFixed32());
assertEquals(7890, message.getOptionalSfixed32());
- assertEquals(1.5f, message.getOptionalFloat());
- assertEquals(1.25, message.getOptionalDouble());
+ assertEquals(1.5f, message.getOptionalFloat(), 0.0f);
+ assertEquals(1.25, message.getOptionalDouble(), 0.0);
assertEquals(true, message.getOptionalBool());
}
@@ -1215,6 +1219,115 @@ public class JsonFormatTest extends TestCase {
+ "}",
JsonFormat.printer().includingDefaultValueFields().print(message));
+ Set<FieldDescriptor> fixedFields = new HashSet<FieldDescriptor>();
+ for (FieldDescriptor fieldDesc : TestAllTypes.getDescriptor().getFields()) {
+ if (fieldDesc.getName().contains("_fixed")) {
+ fixedFields.add(fieldDesc);
+ }
+ }
+
+ assertEquals(
+ "{\n"
+ + " \"optionalFixed32\": 0,\n"
+ + " \"optionalFixed64\": \"0\",\n"
+ + " \"repeatedFixed32\": [],\n"
+ + " \"repeatedFixed64\": []\n"
+ + "}",
+ JsonFormat.printer().includingDefaultValueFields(fixedFields).print(message));
+
+ TestAllTypes messageNonDefaults =
+ message.toBuilder().setOptionalInt64(1234).setOptionalFixed32(3232).build();
+ assertEquals(
+ "{\n"
+ + " \"optionalInt64\": \"1234\",\n"
+ + " \"optionalFixed32\": 3232,\n"
+ + " \"optionalFixed64\": \"0\",\n"
+ + " \"repeatedFixed32\": [],\n"
+ + " \"repeatedFixed64\": []\n"
+ + "}",
+ JsonFormat.printer().includingDefaultValueFields(fixedFields).print(messageNonDefaults));
+
+ try {
+ JsonFormat.printer().includingDefaultValueFields().includingDefaultValueFields();
+ fail("IllegalStateException is expected.");
+ } catch (IllegalStateException e) {
+ // Expected.
+ assertTrue(
+ "Exception message should mention includingDefaultValueFields.",
+ e.getMessage().contains("includingDefaultValueFields"));
+ }
+
+ try {
+ JsonFormat.printer().includingDefaultValueFields().includingDefaultValueFields(fixedFields);
+ fail("IllegalStateException is expected.");
+ } catch (IllegalStateException e) {
+ // Expected.
+ assertTrue(
+ "Exception message should mention includingDefaultValueFields.",
+ e.getMessage().contains("includingDefaultValueFields"));
+ }
+
+ try {
+ JsonFormat.printer().includingDefaultValueFields(fixedFields).includingDefaultValueFields();
+ fail("IllegalStateException is expected.");
+ } catch (IllegalStateException e) {
+ // Expected.
+ assertTrue(
+ "Exception message should mention includingDefaultValueFields.",
+ e.getMessage().contains("includingDefaultValueFields"));
+ }
+
+ try {
+ JsonFormat.printer()
+ .includingDefaultValueFields(fixedFields)
+ .includingDefaultValueFields(fixedFields);
+ fail("IllegalStateException is expected.");
+ } catch (IllegalStateException e) {
+ // Expected.
+ assertTrue(
+ "Exception message should mention includingDefaultValueFields.",
+ e.getMessage().contains("includingDefaultValueFields"));
+ }
+
+ Set<FieldDescriptor> intFields = new HashSet<FieldDescriptor>();
+ for (FieldDescriptor fieldDesc : TestAllTypes.getDescriptor().getFields()) {
+ if (fieldDesc.getName().contains("_int")) {
+ intFields.add(fieldDesc);
+ }
+ }
+
+ try {
+ JsonFormat.printer()
+ .includingDefaultValueFields(intFields)
+ .includingDefaultValueFields(fixedFields);
+ fail("IllegalStateException is expected.");
+ } catch (IllegalStateException e) {
+ // Expected.
+ assertTrue(
+ "Exception message should mention includingDefaultValueFields.",
+ e.getMessage().contains("includingDefaultValueFields"));
+ }
+
+ try {
+ JsonFormat.printer().includingDefaultValueFields(null);
+ fail("IllegalArgumentException is expected.");
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ assertTrue(
+ "Exception message should mention includingDefaultValueFields.",
+ e.getMessage().contains("includingDefaultValueFields"));
+ }
+
+ try {
+ JsonFormat.printer().includingDefaultValueFields(Collections.emptySet());
+ fail("IllegalArgumentException is expected.");
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ assertTrue(
+ "Exception message should mention includingDefaultValueFields.",
+ e.getMessage().contains("includingDefaultValueFields"));
+ }
+
TestMap mapMessage = TestMap.getDefaultInstance();
assertEquals("{\n}", JsonFormat.printer().print(mapMessage));
assertEquals(
@@ -1283,16 +1396,17 @@ public class JsonFormatTest extends TestCase {
assertEquals("{\n}", JsonFormat.printer().includingDefaultValueFields().print(oneofMessage));
oneofMessage = TestOneof.newBuilder().setOneofInt32(42).build();
- assertEquals("{\n \"oneofInt32\": 42\n}",
- JsonFormat.printer().print(oneofMessage));
- assertEquals("{\n \"oneofInt32\": 42\n}",
+ assertEquals("{\n \"oneofInt32\": 42\n}", JsonFormat.printer().print(oneofMessage));
+ assertEquals(
+ "{\n \"oneofInt32\": 42\n}",
JsonFormat.printer().includingDefaultValueFields().print(oneofMessage));
TestOneof.Builder oneofBuilder = TestOneof.newBuilder();
mergeFromJson("{\n" + " \"oneofNullValue\": null \n" + "}", oneofBuilder);
oneofMessage = oneofBuilder.build();
assertEquals("{\n \"oneofNullValue\": null\n}", JsonFormat.printer().print(oneofMessage));
- assertEquals("{\n \"oneofNullValue\": null\n}",
+ assertEquals(
+ "{\n \"oneofNullValue\": null\n}",
JsonFormat.printer().includingDefaultValueFields().print(oneofMessage));
}
@@ -1424,11 +1538,12 @@ public class JsonFormatTest extends TestCase {
// Test that we are not leaking out JSON exceptions.
public void testJsonException() throws Exception {
- InputStream throwingInputStream = new InputStream() {
- public int read() throws IOException {
- throw new IOException("12345");
- }
- };
+ InputStream throwingInputStream =
+ new InputStream() {
+ public int read() throws IOException {
+ throw new IOException("12345");
+ }
+ };
InputStreamReader throwingReader = new InputStreamReader(throwingInputStream);
// When the underlying reader throws IOException, JsonFormat should forward
// through this IOException.