Browse Source

Merge pull request #1710 from chezRong/master

Add minified Json printing functionality
Feng Xiao 9 years ago
parent
commit
aeff638a4c

+ 101 - 26
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:
+     * <pre>
+     * ws = *(
+     * %x20 /              ; Space
+     * %x09 /              ; Horizontal tab
+     * %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);
     }
 
     /**
@@ -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<FieldDescriptor, Object> fieldsToPrint = null;
@@ -689,7 +764,7 @@ public class JsonFormat {
       for (Map.Entry<FieldDescriptor, Object> 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("}");

+ 58 - 0
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));
+  }
+
 }