Skip to content

Commit

Permalink
added minified JSON formatting functionality with test
Browse files Browse the repository at this point in the history
  • Loading branch information
chezRong committed Jul 1, 2016
1 parent 2fe0556 commit 454dbf1
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 26 deletions.
127 changes: 101 additions & 26 deletions java/util/src/main/java/com/google/protobuf/util/JsonFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ private 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);
}

/**
Expand All @@ -111,14 +111,16 @@ public static class Printer {
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;
}

/**
Expand All @@ -131,7 +133,7 @@ public Printer usingTypeRegistry(TypeRegistry registry) {
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);
}

/**
Expand All @@ -141,7 +143,7 @@ public Printer usingTypeRegistry(TypeRegistry registry) {
* {@link Printer}.
*/
public Printer includingDefaultValueFields() {
return new Printer(registry, true, preservingProtoFieldNames);
return new Printer(registry, true, preservingProtoFieldNames, omittingInsignificantWhitespace);
}

/**
Expand All @@ -151,7 +153,27 @@ public Printer includingDefaultValueFields() {
* 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);
}

/**
Expand All @@ -164,7 +186,7 @@ public Printer preservingProtoFieldNames() {
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);
}

Expand Down Expand Up @@ -351,15 +373,55 @@ private void addMessage(Descriptor message) {
}
}

/**
* 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;
}

Expand Down Expand Up @@ -423,6 +485,8 @@ private static final class PrinterImpl {
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();
Expand All @@ -432,12 +496,21 @@ private static class GsonHolder {
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 {
Expand Down Expand Up @@ -568,12 +641,12 @@ private void printAny(MessageOrBuilder message) throws IOException {
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 {
Expand Down Expand Up @@ -661,13 +734,15 @@ private void printListValue(MessageOrBuilder message) throws IOException {
}

/** 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;
Expand All @@ -689,7 +764,7 @@ private void print(MessageOrBuilder message, String typeUrl) throws IOException
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;
}
Expand All @@ -698,17 +773,17 @@ private void print(MessageOrBuilder message, String typeUrl) throws IOException

// Add line-endings for the last field.
if (printedField) {
generator.print("\n");
generator.print(blankOrNewLine);
}
generator.outdent();
generator.print("}");
}

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);
Expand All @@ -725,7 +800,7 @@ private void printRepeatedFieldValue(FieldDescriptor field, Object value) throws
boolean printedElement = false;
for (Object element : (List) value) {
if (printedElement) {
generator.print(", ");
generator.print("," + blankOrSpace);
} else {
printedElement = true;
}
Expand All @@ -742,25 +817,25 @@ private void printMapFieldValue(FieldDescriptor field, Object value) throws IOEx
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) {
Message entry = (Message) element;
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("}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ private void assertRoundTripEquals(Message message, TypeRegistry registry) throw
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);
Expand Down Expand Up @@ -1166,4 +1169,59 @@ public void testPreservingProtoFieldNames() throws Exception {
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));
}

}

0 comments on commit 454dbf1

Please sign in to comment.