From 454dbf16fc81db3b8367342cf425201294004c23 Mon Sep 17 00:00:00 2001 From: chezRong Date: Thu, 23 Jun 2016 17:36:37 +0100 Subject: added minified JSON formatting functionality with test --- .../java/com/google/protobuf/util/JsonFormat.java | 127 ++++++++++++++++----- 1 file changed, 101 insertions(+), 26 deletions(-) (limited to 'java/util/src/main/java') 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 bf70834a..297545e5 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("}"); -- cgit v1.2.3