From 454dbf16fc81db3b8367342cf425201294004c23 Mon Sep 17 00:00:00 2001
From: chezRong
Date: Thu, 23 Jun 2016 17:36:37 +0100
Subject: [PATCH] added minified JSON formatting functionality with test
---
.../com/google/protobuf/util/JsonFormat.java | 127 ++++++++++++++----
.../google/protobuf/util/JsonFormatTest.java | 58 ++++++++
2 files changed, 159 insertions(+), 26 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 bf70834a6b..297545e52a 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
@@ -101,7 +101,7 @@ public class JsonFormat {
* Creates a {@link Printer} with default configurations.
*/
public static Printer printer() {
- return new Printer(TypeRegistry.getEmptyTypeRegistry(), false, false);
+ return new Printer(TypeRegistry.getEmptyTypeRegistry(), false, false, false);
}
/**
@@ -111,14 +111,16 @@ public class JsonFormat {
private final TypeRegistry registry;
private final boolean includingDefaultValueFields;
private final boolean preservingProtoFieldNames;
+ private final boolean omittingInsignificantWhitespace;
private Printer(
TypeRegistry registry,
boolean includingDefaultValueFields,
- boolean preservingProtoFieldNames) {
+ boolean preservingProtoFieldNames, boolean omittingInsignificantWhitespace) {
this.registry = registry;
this.includingDefaultValueFields = includingDefaultValueFields;
this.preservingProtoFieldNames = preservingProtoFieldNames;
+ this.omittingInsignificantWhitespace = omittingInsignificantWhitespace;
}
/**
@@ -131,7 +133,7 @@ public class JsonFormat {
if (this.registry != TypeRegistry.getEmptyTypeRegistry()) {
throw new IllegalArgumentException("Only one registry is allowed.");
}
- return new Printer(registry, includingDefaultValueFields, preservingProtoFieldNames);
+ return new Printer(registry, includingDefaultValueFields, preservingProtoFieldNames, omittingInsignificantWhitespace);
}
/**
@@ -141,7 +143,7 @@ public class JsonFormat {
* {@link Printer}.
*/
public Printer includingDefaultValueFields() {
- return new Printer(registry, true, preservingProtoFieldNames);
+ return new Printer(registry, true, preservingProtoFieldNames, omittingInsignificantWhitespace);
}
/**
@@ -151,7 +153,27 @@ public class JsonFormat {
* current {@link Printer}.
*/
public Printer preservingProtoFieldNames() {
- return new Printer(registry, includingDefaultValueFields, true);
+ return new Printer(registry, 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:
+ *
+ * ws = *(
+ * %x20 / ; Space
+ * %x09 / ; Horizontal tab
+ * %x0A / ; Line feed or New line
+ * %x0D ) ; Carriage return
+ *
+ * See https://tools.ietf.org/html/rfc7159
+ * current {@link Printer}.
+ */
+ public Printer omittingInsignificantWhitespace(){
+ return new Printer(registry, includingDefaultValueFields, preservingProtoFieldNames, true);
}
/**
@@ -164,7 +186,7 @@ public class JsonFormat {
public void appendTo(MessageOrBuilder message, Appendable output) throws IOException {
// TODO(xiaofeng): Investigate the allocation overhead and optimize for
// mobile.
- new PrinterImpl(registry, includingDefaultValueFields, preservingProtoFieldNames, output)
+ new PrinterImpl(registry, includingDefaultValueFields, preservingProtoFieldNames, output, omittingInsignificantWhitespace)
.print(message);
}
@@ -351,15 +373,55 @@ public class JsonFormat {
}
}
+ /**
+ * An interface for json formatting that can be used in
+ * combination with the omittingInsignificantWhitespace() method
+ */
+ interface TextGenerator {
+ void indent();
+ void outdent();
+ void print(final CharSequence text) throws IOException;
+ }
+
+
+ /**
+ * Format the json without indentation
+ */
+ private static final class CompactTextGenerator implements TextGenerator{
+ private final Appendable output;
+
+
+ private CompactTextGenerator(final Appendable output) {
+ this.output = output;
+ }
+
+ /**
+ * ignored by compact printer
+ */
+ public void indent() {}
+
+ /**
+ * ignored by compact printer
+ */
+ public void outdent() {}
+
+ /**
+ * Print text to the output stream.
+ */
+ public void print(final CharSequence text) throws IOException {
+ output.append(text);
+ }
+
+ }
/**
* A TextGenerator adds indentation when writing formatted text.
*/
- private static final class TextGenerator {
+ private static final class PrettyTextGenerator implements TextGenerator{
private final Appendable output;
private final StringBuilder indent = new StringBuilder();
private boolean atStartOfLine = true;
- private TextGenerator(final Appendable output) {
+ private PrettyTextGenerator(final Appendable output) {
this.output = output;
}
@@ -423,6 +485,8 @@ public class JsonFormat {
private final TextGenerator generator;
// We use Gson to help handle string escapes.
private final Gson gson;
+ private final CharSequence blankOrSpace;
+ private final CharSequence blankOrNewLine;
private static class GsonHolder {
private static final Gson DEFAULT_GSON = new GsonBuilder().disableHtmlEscaping().create();
@@ -432,12 +496,21 @@ public class JsonFormat {
TypeRegistry registry,
boolean includingDefaultValueFields,
boolean preservingProtoFieldNames,
- Appendable jsonOutput) {
+ Appendable jsonOutput, boolean omittingInsignificantWhitespace) {
this.registry = registry;
this.includingDefaultValueFields = includingDefaultValueFields;
this.preservingProtoFieldNames = preservingProtoFieldNames;
- this.generator = new TextGenerator(jsonOutput);
this.gson = GsonHolder.DEFAULT_GSON;
+ // json format related properties, determined by printerType
+ if (omittingInsignificantWhitespace) {
+ this.generator = new CompactTextGenerator(jsonOutput);
+ this.blankOrSpace = "";
+ this.blankOrNewLine = "";
+ } else {
+ this.generator = new PrettyTextGenerator(jsonOutput);
+ this.blankOrSpace = " ";
+ this.blankOrNewLine = "\n";
+ }
}
void print(MessageOrBuilder message) throws IOException {
@@ -568,12 +641,12 @@ public class JsonFormat {
if (printer != null) {
// If the type is one of the well-known types, we use a special
// formatting.
- generator.print("{\n");
+ generator.print("{" + blankOrNewLine);
generator.indent();
- generator.print("\"@type\": " + gson.toJson(typeUrl) + ",\n");
- generator.print("\"value\": ");
+ generator.print("\"@type\":" + blankOrSpace + gson.toJson(typeUrl) + "," + blankOrNewLine);
+ generator.print("\"value\":" + blankOrSpace);
printer.print(this, contentMessage);
- generator.print("\n");
+ generator.print(blankOrNewLine);
generator.outdent();
generator.print("}");
} else {
@@ -661,13 +734,15 @@ public class JsonFormat {
}
/** Prints a regular message with an optional type URL. */
- private void print(MessageOrBuilder message, String typeUrl) throws IOException {
- generator.print("{\n");
+
+ private void print(MessageOrBuilder message, String typeUrl)
+ throws IOException {
+ generator.print("{" + blankOrNewLine);
generator.indent();
boolean printedField = false;
if (typeUrl != null) {
- generator.print("\"@type\": " + gson.toJson(typeUrl));
+ generator.print("\"@type\":" + blankOrSpace + gson.toJson(typeUrl));
printedField = true;
}
Map fieldsToPrint = null;
@@ -689,7 +764,7 @@ public class JsonFormat {
for (Map.Entry field : fieldsToPrint.entrySet()) {
if (printedField) {
// Add line-endings for the previous field.
- generator.print(",\n");
+ generator.print("," + blankOrNewLine);
} else {
printedField = true;
}
@@ -698,7 +773,7 @@ public class JsonFormat {
// Add line-endings for the last field.
if (printedField) {
- generator.print("\n");
+ generator.print(blankOrNewLine);
}
generator.outdent();
generator.print("}");
@@ -706,9 +781,9 @@ public class JsonFormat {
private void printField(FieldDescriptor field, Object value) throws IOException {
if (preservingProtoFieldNames) {
- generator.print("\"" + field.getName() + "\": ");
+ generator.print("\"" + field.getName() + "\":" + blankOrSpace);
} else {
- generator.print("\"" + field.getJsonName() + "\": ");
+ generator.print("\"" + field.getJsonName() + "\":" + blankOrSpace);
}
if (field.isMapField()) {
printMapFieldValue(field, value);
@@ -725,7 +800,7 @@ public class JsonFormat {
boolean printedElement = false;
for (Object element : (List) value) {
if (printedElement) {
- generator.print(", ");
+ generator.print("," + blankOrSpace);
} else {
printedElement = true;
}
@@ -742,7 +817,7 @@ public class JsonFormat {
if (keyField == null || valueField == null) {
throw new InvalidProtocolBufferException("Invalid map field.");
}
- generator.print("{\n");
+ generator.print("{" + blankOrNewLine);
generator.indent();
boolean printedElement = false;
for (Object element : (List) value) {
@@ -750,17 +825,17 @@ public class JsonFormat {
Object entryKey = entry.getField(keyField);
Object entryValue = entry.getField(valueField);
if (printedElement) {
- generator.print(",\n");
+ generator.print("," + blankOrNewLine);
} else {
printedElement = true;
}
// Key fields are always double-quoted.
printSingleFieldValue(keyField, entryKey, true);
- generator.print(": ");
+ generator.print(":" + blankOrSpace);
printSingleFieldValue(valueField, entryValue);
}
if (printedElement) {
- generator.print("\n");
+ generator.print(blankOrNewLine);
}
generator.outdent();
generator.print("}");
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 4d9a417d33..e68c7be122 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
@@ -140,6 +140,9 @@ public class JsonFormatTest extends TestCase {
private String toJsonString(Message message) throws IOException {
return JsonFormat.printer().print(message);
}
+ private String toCompactJsonString(Message message) throws IOException{
+ return JsonFormat.printer().omittingInsignificantWhitespace().print(message);
+ }
private void mergeFromJson(String json, Message.Builder builder) throws IOException {
JsonFormat.parser().merge(json, builder);
@@ -1166,4 +1169,59 @@ public class JsonFormatTest extends TestCase {
JsonFormat.parser().merge("{\"optional_int32\": 54321}", builder);
assertEquals(54321, builder.getOptionalInt32());
}
+
+ public void testOmittingInsignificantWhiteSpace() throws Exception {
+ TestAllTypes message = TestAllTypes.newBuilder().setOptionalInt32(12345).build();
+ assertEquals("{" + "\"optionalInt32\":12345" + "}", JsonFormat.printer().omittingInsignificantWhitespace().print(message));
+ TestAllTypes message1 = TestAllTypes.getDefaultInstance();
+ assertEquals("{}", JsonFormat.printer().omittingInsignificantWhitespace().print(message1));
+ TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+ setAllFields(builder);
+ TestAllTypes message2 = builder.build();
+ assertEquals(
+ "{"
+ + "\"optionalInt32\":1234,"
+ + "\"optionalInt64\":\"1234567890123456789\","
+ + "\"optionalUint32\":5678,"
+ + "\"optionalUint64\":\"2345678901234567890\","
+ + "\"optionalSint32\":9012,"
+ + "\"optionalSint64\":\"3456789012345678901\","
+ + "\"optionalFixed32\":3456,"
+ + "\"optionalFixed64\":\"4567890123456789012\","
+ + "\"optionalSfixed32\":7890,"
+ + "\"optionalSfixed64\":\"5678901234567890123\","
+ + "\"optionalFloat\":1.5,"
+ + "\"optionalDouble\":1.25,"
+ + "\"optionalBool\":true,"
+ + "\"optionalString\":\"Hello world!\","
+ + "\"optionalBytes\":\"AAEC\","
+ + "\"optionalNestedMessage\":{"
+ + "\"value\":100"
+ + "},"
+ + "\"optionalNestedEnum\":\"BAR\","
+ + "\"repeatedInt32\":[1234,234],"
+ + "\"repeatedInt64\":[\"1234567890123456789\",\"234567890123456789\"],"
+ + "\"repeatedUint32\":[5678,678],"
+ + "\"repeatedUint64\":[\"2345678901234567890\",\"345678901234567890\"],"
+ + "\"repeatedSint32\":[9012,10],"
+ + "\"repeatedSint64\":[\"3456789012345678901\",\"456789012345678901\"],"
+ + "\"repeatedFixed32\":[3456,456],"
+ + "\"repeatedFixed64\":[\"4567890123456789012\",\"567890123456789012\"],"
+ + "\"repeatedSfixed32\":[7890,890],"
+ + "\"repeatedSfixed64\":[\"5678901234567890123\",\"678901234567890123\"],"
+ + "\"repeatedFloat\":[1.5,11.5],"
+ + "\"repeatedDouble\":[1.25,11.25],"
+ + "\"repeatedBool\":[true,true],"
+ + "\"repeatedString\":[\"Hello world!\",\"ello world!\"],"
+ + "\"repeatedBytes\":[\"AAEC\",\"AQI=\"],"
+ + "\"repeatedNestedMessage\":[{"
+ + "\"value\":100"
+ + "},{"
+ + "\"value\":200"
+ + "}],"
+ + "\"repeatedNestedEnum\":[\"BAR\",\"BAZ\"]"
+ + "}",
+ toCompactJsonString(message2));
+ }
+
}