Index: third_party/protobuf/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java |
diff --git a/third_party/protobuf/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java b/third_party/protobuf/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java |
index 76f3437a6b7a4e628e33269e9cbf7e73756deb88..ac712c94296be1fe92370c1d7a0323ac3bf60b99 100644 |
--- a/third_party/protobuf/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java |
+++ b/third_party/protobuf/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java |
@@ -49,6 +49,7 @@ import com.google.protobuf.Descriptors.EnumDescriptor; |
import com.google.protobuf.Descriptors.EnumValueDescriptor; |
import com.google.protobuf.Descriptors.FieldDescriptor; |
import com.google.protobuf.Descriptors.FileDescriptor; |
+import com.google.protobuf.Descriptors.OneofDescriptor; |
import com.google.protobuf.DoubleValue; |
import com.google.protobuf.Duration; |
import com.google.protobuf.DynamicMessage; |
@@ -60,13 +61,13 @@ import com.google.protobuf.InvalidProtocolBufferException; |
import com.google.protobuf.ListValue; |
import com.google.protobuf.Message; |
import com.google.protobuf.MessageOrBuilder; |
+import com.google.protobuf.NullValue; |
import com.google.protobuf.StringValue; |
import com.google.protobuf.Struct; |
import com.google.protobuf.Timestamp; |
import com.google.protobuf.UInt32Value; |
import com.google.protobuf.UInt64Value; |
import com.google.protobuf.Value; |
- |
import java.io.IOException; |
import java.io.Reader; |
import java.io.StringReader; |
@@ -92,18 +93,17 @@ import java.util.logging.Logger; |
* as well. |
*/ |
public class JsonFormat { |
- private static final Logger logger = |
- Logger.getLogger(JsonFormat.class.getName()); |
+ private static final Logger logger = Logger.getLogger(JsonFormat.class.getName()); |
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); |
} |
- |
+ |
/** |
* A Printer converts protobuf message to JSON format. |
*/ |
@@ -111,27 +111,34 @@ 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; |
} |
- |
+ |
/** |
* Creates a new {@link Printer} using the given registry. The new Printer |
* clones all other configurations from the current {@link Printer}. |
- * |
+ * |
* @throws IllegalArgumentException if a registry is already set. |
*/ |
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); |
} |
/** |
@@ -141,7 +148,8 @@ public class JsonFormat { |
* {@link Printer}. |
*/ |
public Printer includingDefaultValueFields() { |
- return new Printer(registry, true, preservingProtoFieldNames); |
+ return new Printer( |
+ registry, true, preservingProtoFieldNames, omittingInsignificantWhitespace); |
} |
/** |
@@ -151,30 +159,54 @@ 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); |
} |
- |
+ |
/** |
* Converts a protobuf message to JSON format. |
- * |
+ * |
* @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 { |
+ 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); |
} |
/** |
* Converts a protobuf message to JSON format. Throws exceptions if there |
- * are unknown Any types in the message. |
+ * are unknown Any types in the message. |
*/ |
- public String print(MessageOrBuilder message) |
- throws InvalidProtocolBufferException { |
+ public String print(MessageOrBuilder message) throws InvalidProtocolBufferException { |
try { |
StringBuilder builder = new StringBuilder(); |
appendTo(message, builder); |
@@ -192,57 +224,75 @@ public class JsonFormat { |
* Creates a {@link Parser} with default configuration. |
*/ |
public static Parser parser() { |
- return new Parser(TypeRegistry.getEmptyTypeRegistry()); |
+ return new Parser(TypeRegistry.getEmptyTypeRegistry(), false, Parser.DEFAULT_RECURSION_LIMIT); |
} |
- |
+ |
/** |
* A Parser parses JSON to protobuf message. |
*/ |
public static class Parser { |
private final TypeRegistry registry; |
- |
- private Parser(TypeRegistry registry) { |
- this.registry = registry; |
+ private final boolean ignoringUnknownFields; |
+ private final int recursionLimit; |
+ |
+ // The default parsing recursion limit is aligned with the proto binary parser. |
+ private static final int DEFAULT_RECURSION_LIMIT = 100; |
+ |
+ private Parser(TypeRegistry registry, boolean ignoreUnknownFields, int recursionLimit) { |
+ this.registry = registry; |
+ this.ignoringUnknownFields = ignoreUnknownFields; |
+ this.recursionLimit = recursionLimit; |
} |
- |
+ |
/** |
* Creates a new {@link Parser} using the given registry. The new Parser |
* clones all other configurations from this Parser. |
- * |
+ * |
* @throws IllegalArgumentException if a registry is already set. |
*/ |
public Parser usingTypeRegistry(TypeRegistry registry) { |
if (this.registry != TypeRegistry.getEmptyTypeRegistry()) { |
throw new IllegalArgumentException("Only one registry is allowed."); |
} |
- return new Parser(registry); |
+ return new Parser(registry, ignoringUnknownFields, recursionLimit); |
+ } |
+ |
+ /** |
+ * Creates a new {@link Parser} configured to not throw an exception when an unknown field is |
+ * encountered. The new Parser clones all other configurations from this Parser. |
+ */ |
+ public Parser ignoringUnknownFields() { |
+ return new Parser(this.registry, true, recursionLimit); |
} |
- |
+ |
/** |
* Parses from JSON into a protobuf message. |
- * |
+ * |
* @throws InvalidProtocolBufferException if the input is not valid JSON |
* format or there are unknown fields in the input. |
*/ |
- public void merge(String json, Message.Builder builder) |
- throws InvalidProtocolBufferException { |
+ public void merge(String json, Message.Builder builder) throws InvalidProtocolBufferException { |
// TODO(xiaofeng): Investigate the allocation overhead and optimize for |
// mobile. |
- new ParserImpl(registry).merge(json, builder); |
+ new ParserImpl(registry, ignoringUnknownFields, recursionLimit).merge(json, builder); |
} |
- |
+ |
/** |
* Parses from JSON into a protobuf message. |
- * |
+ * |
* @throws InvalidProtocolBufferException if the input is not valid JSON |
* format or there are unknown fields in the input. |
* @throws IOException if reading from the input throws. |
*/ |
- public void merge(Reader json, Message.Builder builder) |
- throws IOException { |
+ public void merge(Reader json, Message.Builder builder) throws IOException { |
// TODO(xiaofeng): Investigate the allocation overhead and optimize for |
// mobile. |
- new ParserImpl(registry).merge(json, builder); |
+ new ParserImpl(registry, ignoringUnknownFields, recursionLimit).merge(json, builder); |
+ } |
+ |
+ // For testing only. |
+ Parser usingRecursionLimit(int recursionLimit) { |
+ return new Parser(registry, ignoringUnknownFields, recursionLimit); |
} |
} |
@@ -255,8 +305,8 @@ public class JsonFormat { |
*/ |
public static class TypeRegistry { |
private static class EmptyTypeRegistryHolder { |
- private static final TypeRegistry EMPTY = new TypeRegistry( |
- Collections.<String, Descriptor>emptyMap()); |
+ private static final TypeRegistry EMPTY = |
+ new TypeRegistry(Collections.<String, Descriptor>emptyMap()); |
} |
public static TypeRegistry getEmptyTypeRegistry() { |
@@ -293,8 +343,7 @@ public class JsonFormat { |
*/ |
public Builder add(Descriptor messageType) { |
if (types == null) { |
- throw new IllegalStateException( |
- "A TypeRegistry.Builer can only be used once."); |
+ throw new IllegalStateException("A TypeRegistry.Builer can only be used once."); |
} |
addFile(messageType.getFile()); |
return this; |
@@ -306,8 +355,7 @@ public class JsonFormat { |
*/ |
public Builder add(Iterable<Descriptor> messageTypes) { |
if (types == null) { |
- throw new IllegalStateException( |
- "A TypeRegistry.Builer can only be used once."); |
+ throw new IllegalStateException("A TypeRegistry.Builer can only be used once."); |
} |
for (Descriptor type : messageTypes) { |
addFile(type.getFile()); |
@@ -345,8 +393,7 @@ public class JsonFormat { |
} |
if (types.containsKey(message.getFullName())) { |
- logger.warning("Type " + message.getFullName() |
- + " is added multiple times."); |
+ logger.warning("Type " + message.getFullName() + " is added multiple times."); |
return; |
} |
@@ -354,20 +401,58 @@ public class JsonFormat { |
} |
private final Set<String> files = new HashSet<String>(); |
- private Map<String, Descriptor> types = |
- new HashMap<String, Descriptor>(); |
+ private Map<String, Descriptor> types = new HashMap<String, Descriptor>(); |
} |
} |
/** |
+ * 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; |
} |
@@ -387,8 +472,7 @@ public class JsonFormat { |
public void outdent() { |
final int length = indent.length(); |
if (length < 2) { |
- throw new IllegalArgumentException( |
- " Outdent() without matching Indent()."); |
+ throw new IllegalArgumentException(" Outdent() without matching Indent()."); |
} |
indent.delete(length - 2, length); |
} |
@@ -432,6 +516,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(); |
@@ -441,54 +527,60 @@ 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 { |
- WellKnownTypePrinter specialPrinter = wellKnownTypePrinters.get( |
- message.getDescriptorForType().getFullName()); |
+ WellKnownTypePrinter specialPrinter = |
+ wellKnownTypePrinters.get(message.getDescriptorForType().getFullName()); |
if (specialPrinter != null) { |
specialPrinter.print(this, message); |
return; |
} |
print(message, null); |
} |
- |
+ |
private interface WellKnownTypePrinter { |
- void print(PrinterImpl printer, MessageOrBuilder message) |
- throws IOException; |
- } |
- |
- private static final Map<String, WellKnownTypePrinter> |
- wellKnownTypePrinters = buildWellKnownTypePrinters(); |
- |
- private static Map<String, WellKnownTypePrinter> |
- buildWellKnownTypePrinters() { |
- Map<String, WellKnownTypePrinter> printers = |
- new HashMap<String, WellKnownTypePrinter>(); |
+ void print(PrinterImpl printer, MessageOrBuilder message) throws IOException; |
+ } |
+ |
+ private static final Map<String, WellKnownTypePrinter> wellKnownTypePrinters = |
+ buildWellKnownTypePrinters(); |
+ |
+ private static Map<String, WellKnownTypePrinter> buildWellKnownTypePrinters() { |
+ Map<String, WellKnownTypePrinter> printers = new HashMap<String, WellKnownTypePrinter>(); |
// Special-case Any. |
- printers.put(Any.getDescriptor().getFullName(), |
+ printers.put( |
+ Any.getDescriptor().getFullName(), |
new WellKnownTypePrinter() { |
- @Override |
- public void print(PrinterImpl printer, MessageOrBuilder message) |
- throws IOException { |
- printer.printAny(message); |
- } |
- }); |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { |
+ printer.printAny(message); |
+ } |
+ }); |
// Special-case wrapper types. |
- WellKnownTypePrinter wrappersPrinter = new WellKnownTypePrinter() { |
- @Override |
- public void print(PrinterImpl printer, MessageOrBuilder message) |
- throws IOException { |
- printer.printWrapper(message); |
- |
- } |
- }; |
+ WellKnownTypePrinter wrappersPrinter = |
+ new WellKnownTypePrinter() { |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { |
+ printer.printWrapper(message); |
+ } |
+ }; |
printers.put(BoolValue.getDescriptor().getFullName(), wrappersPrinter); |
printers.put(Int32Value.getDescriptor().getFullName(), wrappersPrinter); |
printers.put(UInt32Value.getDescriptor().getFullName(), wrappersPrinter); |
@@ -499,70 +591,75 @@ public class JsonFormat { |
printers.put(FloatValue.getDescriptor().getFullName(), wrappersPrinter); |
printers.put(DoubleValue.getDescriptor().getFullName(), wrappersPrinter); |
// Special-case Timestamp. |
- printers.put(Timestamp.getDescriptor().getFullName(), |
+ printers.put( |
+ Timestamp.getDescriptor().getFullName(), |
new WellKnownTypePrinter() { |
- @Override |
- public void print(PrinterImpl printer, MessageOrBuilder message) |
- throws IOException { |
- printer.printTimestamp(message); |
- } |
- }); |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { |
+ printer.printTimestamp(message); |
+ } |
+ }); |
// Special-case Duration. |
- printers.put(Duration.getDescriptor().getFullName(), |
+ printers.put( |
+ Duration.getDescriptor().getFullName(), |
new WellKnownTypePrinter() { |
- @Override |
- public void print(PrinterImpl printer, MessageOrBuilder message) |
- throws IOException { |
- printer.printDuration(message); |
- } |
- }); |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { |
+ printer.printDuration(message); |
+ } |
+ }); |
// Special-case FieldMask. |
- printers.put(FieldMask.getDescriptor().getFullName(), |
+ printers.put( |
+ FieldMask.getDescriptor().getFullName(), |
new WellKnownTypePrinter() { |
- @Override |
- public void print(PrinterImpl printer, MessageOrBuilder message) |
- throws IOException { |
- printer.printFieldMask(message); |
- } |
- }); |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { |
+ printer.printFieldMask(message); |
+ } |
+ }); |
// Special-case Struct. |
- printers.put(Struct.getDescriptor().getFullName(), |
+ printers.put( |
+ Struct.getDescriptor().getFullName(), |
new WellKnownTypePrinter() { |
- @Override |
- public void print(PrinterImpl printer, MessageOrBuilder message) |
- throws IOException { |
- printer.printStruct(message); |
- } |
- }); |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { |
+ printer.printStruct(message); |
+ } |
+ }); |
// Special-case Value. |
- printers.put(Value.getDescriptor().getFullName(), |
+ printers.put( |
+ Value.getDescriptor().getFullName(), |
new WellKnownTypePrinter() { |
- @Override |
- public void print(PrinterImpl printer, MessageOrBuilder message) |
- throws IOException { |
- printer.printValue(message); |
- } |
- }); |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { |
+ printer.printValue(message); |
+ } |
+ }); |
// Special-case ListValue. |
- printers.put(ListValue.getDescriptor().getFullName(), |
+ printers.put( |
+ ListValue.getDescriptor().getFullName(), |
new WellKnownTypePrinter() { |
- @Override |
- public void print(PrinterImpl printer, MessageOrBuilder message) |
- throws IOException { |
- printer.printListValue(message); |
- } |
- }); |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { |
+ printer.printListValue(message); |
+ } |
+ }); |
return printers; |
} |
- |
+ |
/** Prints google.protobuf.Any */ |
private void printAny(MessageOrBuilder message) throws IOException { |
+ if (Any.getDefaultInstance().equals(message)) { |
+ generator.print("{}"); |
+ return; |
+ } |
Descriptor descriptor = message.getDescriptorForType(); |
FieldDescriptor typeUrlField = descriptor.findFieldByName("type_url"); |
FieldDescriptor valueField = descriptor.findFieldByName("value"); |
// Validates type of the message. Note that we can't just cast the message |
- // to com.google.protobuf.Any because it might be a DynamicMessage. |
- if (typeUrlField == null || valueField == null |
+ // to com.google.protobuf.Any because it might be a DynamicMessage. |
+ if (typeUrlField == null |
+ || valueField == null |
|| typeUrlField.getType() != FieldDescriptor.Type.STRING |
|| valueField.getType() != FieldDescriptor.Type.BYTES) { |
throw new InvalidProtocolBufferException("Invalid Any type."); |
@@ -571,22 +668,21 @@ public class JsonFormat { |
String typeName = getTypeName(typeUrl); |
Descriptor type = registry.find(typeName); |
if (type == null) { |
- throw new InvalidProtocolBufferException( |
- "Cannot find type for url: " + typeUrl); |
+ throw new InvalidProtocolBufferException("Cannot find type for url: " + typeUrl); |
} |
ByteString content = (ByteString) message.getField(valueField); |
- Message contentMessage = DynamicMessage.getDefaultInstance(type) |
- .getParserForType().parseFrom(content); |
+ Message contentMessage = |
+ DynamicMessage.getDefaultInstance(type).getParserForType().parseFrom(content); |
WellKnownTypePrinter printer = wellKnownTypePrinters.get(typeName); |
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 { |
@@ -594,7 +690,7 @@ public class JsonFormat { |
print(contentMessage, typeUrl); |
} |
} |
- |
+ |
/** Prints wrapper types (e.g., google.protobuf.Int32Value) */ |
private void printWrapper(MessageOrBuilder message) throws IOException { |
Descriptor descriptor = message.getDescriptorForType(); |
@@ -606,7 +702,7 @@ public class JsonFormat { |
// the whole message. |
printSingleFieldValue(valueField, message.getField(valueField)); |
} |
- |
+ |
private ByteString toByteString(MessageOrBuilder message) { |
if (message instanceof Message) { |
return ((Message) message).toByteString(); |
@@ -614,26 +710,25 @@ public class JsonFormat { |
return ((Message.Builder) message).build().toByteString(); |
} |
} |
- |
+ |
/** Prints google.protobuf.Timestamp */ |
private void printTimestamp(MessageOrBuilder message) throws IOException { |
Timestamp value = Timestamp.parseFrom(toByteString(message)); |
- generator.print("\"" + TimeUtil.toString(value) + "\""); |
+ generator.print("\"" + Timestamps.toString(value) + "\""); |
} |
- |
+ |
/** Prints google.protobuf.Duration */ |
private void printDuration(MessageOrBuilder message) throws IOException { |
Duration value = Duration.parseFrom(toByteString(message)); |
- generator.print("\"" + TimeUtil.toString(value) + "\""); |
- |
+ generator.print("\"" + Durations.toString(value) + "\""); |
} |
- |
+ |
/** Prints google.protobuf.FieldMask */ |
private void printFieldMask(MessageOrBuilder message) throws IOException { |
FieldMask value = FieldMask.parseFrom(toByteString(message)); |
- generator.print("\"" + FieldMaskUtil.toString(value) + "\""); |
+ generator.print("\"" + FieldMaskUtil.toJsonString(value) + "\""); |
} |
- |
+ |
/** Prints google.protobuf.Struct */ |
private void printStruct(MessageOrBuilder message) throws IOException { |
Descriptor descriptor = message.getDescriptorForType(); |
@@ -644,7 +739,7 @@ public class JsonFormat { |
// Struct is formatted as a map object. |
printMapFieldValue(field, message.getField(field)); |
} |
- |
+ |
/** Prints google.protobuf.Value */ |
private void printValue(MessageOrBuilder message) throws IOException { |
// For a Value message, only the value of the field is formatted. |
@@ -663,7 +758,7 @@ public class JsonFormat { |
printSingleFieldValue(entry.getKey(), entry.getValue()); |
} |
} |
- |
+ |
/** Prints google.protobuf.ListValue */ |
private void printListValue(MessageOrBuilder message) throws IOException { |
Descriptor descriptor = message.getDescriptorForType(); |
@@ -675,26 +770,31 @@ 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; |
if (includingDefaultValueFields) { |
fieldsToPrint = new TreeMap<FieldDescriptor, Object>(); |
for (FieldDescriptor field : message.getDescriptorForType().getFields()) { |
- if (field.isOptional() |
- && field.getJavaType() == FieldDescriptor.JavaType.MESSAGE |
- && !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; |
+ if (field.isOptional()) { |
+ if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE |
+ && !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 |
+ continue; |
+ } |
} |
fieldsToPrint.put(field, message.getField(field)); |
} |
@@ -704,27 +804,26 @@ 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; |
} |
printField(field.getKey(), field.getValue()); |
} |
- |
+ |
// 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 { |
+ 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); |
@@ -734,15 +833,14 @@ public class JsonFormat { |
printSingleFieldValue(field, value); |
} |
} |
- |
+ |
@SuppressWarnings("rawtypes") |
- private void printRepeatedFieldValue(FieldDescriptor field, Object value) |
- throws IOException { |
+ private void printRepeatedFieldValue(FieldDescriptor field, Object value) throws IOException { |
generator.print("["); |
boolean printedElement = false; |
for (Object element : (List) value) { |
if (printedElement) { |
- generator.print(", "); |
+ generator.print("," + blankOrSpace); |
} else { |
printedElement = true; |
} |
@@ -750,17 +848,16 @@ public class JsonFormat { |
} |
generator.print("]"); |
} |
- |
+ |
@SuppressWarnings("rawtypes") |
- private void printMapFieldValue(FieldDescriptor field, Object value) |
- throws IOException { |
+ private void printMapFieldValue(FieldDescriptor field, Object value) throws IOException { |
Descriptor type = field.getMessageType(); |
FieldDescriptor keyField = type.findFieldByName("key"); |
FieldDescriptor valueField = type.findFieldByName("value"); |
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) { |
@@ -768,36 +865,35 @@ 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("}"); |
} |
- |
- private void printSingleFieldValue(FieldDescriptor field, Object value) |
- throws IOException { |
+ |
+ private void printSingleFieldValue(FieldDescriptor field, Object value) throws IOException { |
printSingleFieldValue(field, value, false); |
} |
/** |
* Prints a field's value in JSON format. |
- * |
+ * |
* @param alwaysWithQuotes whether to always add double-quotes to primitive |
* types. |
*/ |
private void printSingleFieldValue( |
- final FieldDescriptor field, final Object value, |
- boolean alwaysWithQuotes) throws IOException { |
+ final FieldDescriptor field, final Object value, boolean alwaysWithQuotes) |
+ throws IOException { |
switch (field.getType()) { |
case INT32: |
case SINT32: |
@@ -851,7 +947,7 @@ public class JsonFormat { |
} |
} |
break; |
- |
+ |
case DOUBLE: |
Double doubleValue = (Double) value; |
if (doubleValue.isNaN()) { |
@@ -895,15 +991,13 @@ public class JsonFormat { |
case BYTES: |
generator.print("\""); |
- generator.print( |
- BaseEncoding.base64().encode(((ByteString) value).toByteArray())); |
+ generator.print(BaseEncoding.base64().encode(((ByteString) value).toByteArray())); |
generator.print("\""); |
break; |
case ENUM: |
// Special-case google.protobuf.NullValue (it's an Enum). |
- if (field.getEnumType().getFullName().equals( |
- "google.protobuf.NullValue")) { |
+ if (field.getEnumType().getFullName().equals("google.protobuf.NullValue")) { |
// No matter what value it contains, we always print it as "null". |
if (alwaysWithQuotes) { |
generator.print("\""); |
@@ -914,11 +1008,9 @@ public class JsonFormat { |
} |
} else { |
if (((EnumValueDescriptor) value).getIndex() == -1) { |
- generator.print( |
- String.valueOf(((EnumValueDescriptor) value).getNumber())); |
+ generator.print(String.valueOf(((EnumValueDescriptor) value).getNumber())); |
} else { |
- generator.print( |
- "\"" + ((EnumValueDescriptor) value).getName() + "\""); |
+ generator.print("\"" + ((EnumValueDescriptor) value).getName() + "\""); |
} |
} |
break; |
@@ -947,40 +1039,40 @@ public class JsonFormat { |
} else { |
// Pull off the most-significant bit so that BigInteger doesn't think |
// the number is negative, then set it again using setBit(). |
- return BigInteger.valueOf(value & Long.MAX_VALUE) |
- .setBit(Long.SIZE - 1).toString(); |
+ return BigInteger.valueOf(value & Long.MAX_VALUE).setBit(Long.SIZE - 1).toString(); |
} |
} |
- |
- private static String getTypeName(String typeUrl) |
- throws InvalidProtocolBufferException { |
+ private static String getTypeName(String typeUrl) throws InvalidProtocolBufferException { |
String[] parts = typeUrl.split("/"); |
if (parts.length == 1) { |
- throw new InvalidProtocolBufferException( |
- "Invalid type url found: " + typeUrl); |
+ throw new InvalidProtocolBufferException("Invalid type url found: " + typeUrl); |
} |
return parts[parts.length - 1]; |
} |
- |
+ |
private static class ParserImpl { |
private final TypeRegistry registry; |
private final JsonParser jsonParser; |
- |
- ParserImpl(TypeRegistry registry) { |
+ private final boolean ignoringUnknownFields; |
+ private final int recursionLimit; |
+ private int currentDepth; |
+ |
+ ParserImpl(TypeRegistry registry, boolean ignoreUnknownFields, int recursionLimit) { |
this.registry = registry; |
+ this.ignoringUnknownFields = ignoreUnknownFields; |
this.jsonParser = new JsonParser(); |
+ this.recursionLimit = recursionLimit; |
+ this.currentDepth = 0; |
} |
- |
- void merge(Reader json, Message.Builder builder) |
- throws IOException { |
+ |
+ void merge(Reader json, Message.Builder builder) throws IOException { |
JsonReader reader = new JsonReader(json); |
reader.setLenient(false); |
merge(jsonParser.parse(reader), builder); |
} |
- |
- void merge(String json, Message.Builder builder) |
- throws InvalidProtocolBufferException { |
+ |
+ void merge(String json, Message.Builder builder) throws InvalidProtocolBufferException { |
try { |
JsonReader reader = new JsonReader(new StringReader(json)); |
reader.setLenient(false); |
@@ -992,35 +1084,36 @@ public class JsonFormat { |
throw new InvalidProtocolBufferException(e.getMessage()); |
} |
} |
- |
+ |
private interface WellKnownTypeParser { |
void merge(ParserImpl parser, JsonElement json, Message.Builder builder) |
throws InvalidProtocolBufferException; |
} |
- |
+ |
private static final Map<String, WellKnownTypeParser> wellKnownTypeParsers = |
buildWellKnownTypeParsers(); |
- |
- private static Map<String, WellKnownTypeParser> |
- buildWellKnownTypeParsers() { |
- Map<String, WellKnownTypeParser> parsers = |
- new HashMap<String, WellKnownTypeParser>(); |
+ |
+ private static Map<String, WellKnownTypeParser> buildWellKnownTypeParsers() { |
+ Map<String, WellKnownTypeParser> parsers = new HashMap<String, WellKnownTypeParser>(); |
// Special-case Any. |
- parsers.put(Any.getDescriptor().getFullName(), new WellKnownTypeParser() { |
- @Override |
- public void merge(ParserImpl parser, JsonElement json, |
- Message.Builder builder) throws InvalidProtocolBufferException { |
- parser.mergeAny(json, builder); |
- } |
- }); |
+ parsers.put( |
+ Any.getDescriptor().getFullName(), |
+ new WellKnownTypeParser() { |
+ @Override |
+ public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ parser.mergeAny(json, builder); |
+ } |
+ }); |
// Special-case wrapper types. |
- WellKnownTypeParser wrappersPrinter = new WellKnownTypeParser() { |
- @Override |
- public void merge(ParserImpl parser, JsonElement json, |
- Message.Builder builder) throws InvalidProtocolBufferException { |
- parser.mergeWrapper(json, builder); |
- } |
- }; |
+ WellKnownTypeParser wrappersPrinter = |
+ new WellKnownTypeParser() { |
+ @Override |
+ public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ parser.mergeWrapper(json, builder); |
+ } |
+ }; |
parsers.put(BoolValue.getDescriptor().getFullName(), wrappersPrinter); |
parsers.put(Int32Value.getDescriptor().getFullName(), wrappersPrinter); |
parsers.put(UInt32Value.getDescriptor().getFullName(), wrappersPrinter); |
@@ -1031,82 +1124,86 @@ public class JsonFormat { |
parsers.put(FloatValue.getDescriptor().getFullName(), wrappersPrinter); |
parsers.put(DoubleValue.getDescriptor().getFullName(), wrappersPrinter); |
// Special-case Timestamp. |
- parsers.put(Timestamp.getDescriptor().getFullName(), |
+ parsers.put( |
+ Timestamp.getDescriptor().getFullName(), |
new WellKnownTypeParser() { |
- @Override |
- public void merge(ParserImpl parser, JsonElement json, |
- Message.Builder builder) throws InvalidProtocolBufferException { |
- parser.mergeTimestamp(json, builder); |
- } |
- }); |
+ @Override |
+ public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ parser.mergeTimestamp(json, builder); |
+ } |
+ }); |
// Special-case Duration. |
- parsers.put(Duration.getDescriptor().getFullName(), |
+ parsers.put( |
+ Duration.getDescriptor().getFullName(), |
new WellKnownTypeParser() { |
- @Override |
- public void merge(ParserImpl parser, JsonElement json, |
- Message.Builder builder) throws InvalidProtocolBufferException { |
- parser.mergeDuration(json, builder); |
- } |
- }); |
+ @Override |
+ public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ parser.mergeDuration(json, builder); |
+ } |
+ }); |
// Special-case FieldMask. |
- parsers.put(FieldMask.getDescriptor().getFullName(), |
+ parsers.put( |
+ FieldMask.getDescriptor().getFullName(), |
new WellKnownTypeParser() { |
- @Override |
- public void merge(ParserImpl parser, JsonElement json, |
- Message.Builder builder) throws InvalidProtocolBufferException { |
- parser.mergeFieldMask(json, builder); |
- } |
- }); |
+ @Override |
+ public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ parser.mergeFieldMask(json, builder); |
+ } |
+ }); |
// Special-case Struct. |
- parsers.put(Struct.getDescriptor().getFullName(), |
+ parsers.put( |
+ Struct.getDescriptor().getFullName(), |
new WellKnownTypeParser() { |
- @Override |
- public void merge(ParserImpl parser, JsonElement json, |
- Message.Builder builder) throws InvalidProtocolBufferException { |
- parser.mergeStruct(json, builder); |
- } |
- }); |
+ @Override |
+ public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ parser.mergeStruct(json, builder); |
+ } |
+ }); |
// Special-case ListValue. |
- parsers.put(ListValue.getDescriptor().getFullName(), |
+ parsers.put( |
+ ListValue.getDescriptor().getFullName(), |
new WellKnownTypeParser() { |
- @Override |
- public void merge(ParserImpl parser, JsonElement json, |
- Message.Builder builder) throws InvalidProtocolBufferException { |
- parser.mergeListValue(json, builder); |
- } |
- }); |
+ @Override |
+ public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ parser.mergeListValue(json, builder); |
+ } |
+ }); |
// Special-case Value. |
- parsers.put(Value.getDescriptor().getFullName(), |
+ parsers.put( |
+ Value.getDescriptor().getFullName(), |
new WellKnownTypeParser() { |
- @Override |
- public void merge(ParserImpl parser, JsonElement json, |
- Message.Builder builder) throws InvalidProtocolBufferException { |
- parser.mergeValue(json, builder); |
- } |
- }); |
+ @Override |
+ public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ parser.mergeValue(json, builder); |
+ } |
+ }); |
return parsers; |
} |
- |
+ |
private void merge(JsonElement json, Message.Builder builder) |
throws InvalidProtocolBufferException { |
- WellKnownTypeParser specialParser = wellKnownTypeParsers.get( |
- builder.getDescriptorForType().getFullName()); |
+ WellKnownTypeParser specialParser = |
+ wellKnownTypeParsers.get(builder.getDescriptorForType().getFullName()); |
if (specialParser != null) { |
specialParser.merge(this, json, builder); |
return; |
} |
mergeMessage(json, builder, false); |
} |
- |
+ |
// Maps from camel-case field names to FieldDescriptor. |
private final Map<Descriptor, Map<String, FieldDescriptor>> fieldNameMaps = |
new HashMap<Descriptor, Map<String, FieldDescriptor>>(); |
- |
- private Map<String, FieldDescriptor> getFieldNameMap( |
- Descriptor descriptor) { |
+ |
+ private Map<String, FieldDescriptor> getFieldNameMap(Descriptor descriptor) { |
if (!fieldNameMaps.containsKey(descriptor)) { |
- Map<String, FieldDescriptor> fieldNameMap = |
- new HashMap<String, FieldDescriptor>(); |
+ Map<String, FieldDescriptor> fieldNameMap = new HashMap<String, FieldDescriptor>(); |
for (FieldDescriptor field : descriptor.getFields()) { |
fieldNameMap.put(field.getName(), field); |
fieldNameMap.put(field.getJsonName(), field); |
@@ -1116,64 +1213,67 @@ public class JsonFormat { |
} |
return fieldNameMaps.get(descriptor); |
} |
- |
- private void mergeMessage(JsonElement json, Message.Builder builder, |
- boolean skipTypeUrl) throws InvalidProtocolBufferException { |
+ |
+ private void mergeMessage(JsonElement json, Message.Builder builder, boolean skipTypeUrl) |
+ throws InvalidProtocolBufferException { |
if (!(json instanceof JsonObject)) { |
- throw new InvalidProtocolBufferException( |
- "Expect message object but got: " + json); |
+ throw new InvalidProtocolBufferException("Expect message object but got: " + json); |
} |
JsonObject object = (JsonObject) json; |
- Map<String, FieldDescriptor> fieldNameMap = |
- getFieldNameMap(builder.getDescriptorForType()); |
+ Map<String, FieldDescriptor> fieldNameMap = getFieldNameMap(builder.getDescriptorForType()); |
for (Map.Entry<String, JsonElement> entry : object.entrySet()) { |
if (skipTypeUrl && entry.getKey().equals("@type")) { |
continue; |
} |
FieldDescriptor field = fieldNameMap.get(entry.getKey()); |
if (field == null) { |
+ if (ignoringUnknownFields) { |
+ continue; |
+ } |
throw new InvalidProtocolBufferException( |
- "Cannot find field: " + entry.getKey() + " in message " |
- + builder.getDescriptorForType().getFullName()); |
+ "Cannot find field: " |
+ + entry.getKey() |
+ + " in message " |
+ + builder.getDescriptorForType().getFullName()); |
} |
mergeField(field, entry.getValue(), builder); |
} |
} |
- |
+ |
private void mergeAny(JsonElement json, Message.Builder builder) |
throws InvalidProtocolBufferException { |
Descriptor descriptor = builder.getDescriptorForType(); |
FieldDescriptor typeUrlField = descriptor.findFieldByName("type_url"); |
FieldDescriptor valueField = descriptor.findFieldByName("value"); |
// Validates type of the message. Note that we can't just cast the message |
- // to com.google.protobuf.Any because it might be a DynamicMessage. |
- if (typeUrlField == null || valueField == null |
+ // to com.google.protobuf.Any because it might be a DynamicMessage. |
+ if (typeUrlField == null |
+ || valueField == null |
|| typeUrlField.getType() != FieldDescriptor.Type.STRING |
|| valueField.getType() != FieldDescriptor.Type.BYTES) { |
throw new InvalidProtocolBufferException("Invalid Any type."); |
} |
- |
+ |
if (!(json instanceof JsonObject)) { |
- throw new InvalidProtocolBufferException( |
- "Expect message object but got: " + json); |
+ throw new InvalidProtocolBufferException("Expect message object but got: " + json); |
} |
JsonObject object = (JsonObject) json; |
+ if (object.entrySet().isEmpty()) { |
+ return; // builder never modified, so it will end up building the default instance of Any |
+ } |
JsonElement typeUrlElement = object.get("@type"); |
if (typeUrlElement == null) { |
- throw new InvalidProtocolBufferException( |
- "Missing type url when parsing: " + json); |
+ throw new InvalidProtocolBufferException("Missing type url when parsing: " + json); |
} |
String typeUrl = typeUrlElement.getAsString(); |
Descriptor contentType = registry.find(getTypeName(typeUrl)); |
if (contentType == null) { |
- throw new InvalidProtocolBufferException( |
- "Cannot resolve type: " + typeUrl); |
+ throw new InvalidProtocolBufferException("Cannot resolve type: " + typeUrl); |
} |
builder.setField(typeUrlField, typeUrl); |
Message.Builder contentBuilder = |
DynamicMessage.getDefaultInstance(contentType).newBuilderForType(); |
- WellKnownTypeParser specialParser = |
- wellKnownTypeParsers.get(contentType.getFullName()); |
+ WellKnownTypeParser specialParser = wellKnownTypeParsers.get(contentType.getFullName()); |
if (specialParser != null) { |
JsonElement value = object.get("value"); |
if (value != null) { |
@@ -1184,35 +1284,33 @@ public class JsonFormat { |
} |
builder.setField(valueField, contentBuilder.build().toByteString()); |
} |
- |
+ |
private void mergeFieldMask(JsonElement json, Message.Builder builder) |
throws InvalidProtocolBufferException { |
- FieldMask value = FieldMaskUtil.fromString(json.getAsString()); |
+ FieldMask value = FieldMaskUtil.fromJsonString(json.getAsString()); |
builder.mergeFrom(value.toByteString()); |
} |
- |
+ |
private void mergeTimestamp(JsonElement json, Message.Builder builder) |
throws InvalidProtocolBufferException { |
try { |
- Timestamp value = TimeUtil.parseTimestamp(json.getAsString()); |
+ Timestamp value = Timestamps.parse(json.getAsString()); |
builder.mergeFrom(value.toByteString()); |
} catch (ParseException e) { |
- throw new InvalidProtocolBufferException( |
- "Failed to parse timestamp: " + json); |
+ throw new InvalidProtocolBufferException("Failed to parse timestamp: " + json); |
} |
} |
- |
+ |
private void mergeDuration(JsonElement json, Message.Builder builder) |
throws InvalidProtocolBufferException { |
try { |
- Duration value = TimeUtil.parseDuration(json.getAsString()); |
+ Duration value = Durations.parse(json.getAsString()); |
builder.mergeFrom(value.toByteString()); |
} catch (ParseException e) { |
- throw new InvalidProtocolBufferException( |
- "Failed to parse duration: " + json); |
+ throw new InvalidProtocolBufferException("Failed to parse duration: " + json); |
} |
} |
- |
+ |
private void mergeStruct(JsonElement json, Message.Builder builder) |
throws InvalidProtocolBufferException { |
Descriptor descriptor = builder.getDescriptorForType(); |
@@ -1232,21 +1330,18 @@ public class JsonFormat { |
} |
mergeRepeatedField(field, json, builder); |
} |
- |
+ |
private void mergeValue(JsonElement json, Message.Builder builder) |
throws InvalidProtocolBufferException { |
Descriptor type = builder.getDescriptorForType(); |
if (json instanceof JsonPrimitive) { |
JsonPrimitive primitive = (JsonPrimitive) json; |
if (primitive.isBoolean()) { |
- builder.setField(type.findFieldByName("bool_value"), |
- primitive.getAsBoolean()); |
+ builder.setField(type.findFieldByName("bool_value"), primitive.getAsBoolean()); |
} else if (primitive.isNumber()) { |
- builder.setField(type.findFieldByName("number_value"), |
- primitive.getAsDouble()); |
+ builder.setField(type.findFieldByName("number_value"), primitive.getAsDouble()); |
} else { |
- builder.setField(type.findFieldByName("string_value"), |
- primitive.getAsString()); |
+ builder.setField(type.findFieldByName("string_value"), primitive.getAsString()); |
} |
} else if (json instanceof JsonObject) { |
FieldDescriptor field = type.findFieldByName("struct_value"); |
@@ -1258,24 +1353,26 @@ public class JsonFormat { |
Message.Builder listBuilder = builder.newBuilderForField(field); |
merge(json, listBuilder); |
builder.setField(field, listBuilder.build()); |
+ } else if (json instanceof JsonNull) { |
+ builder.setField( |
+ type.findFieldByName("null_value"), NullValue.NULL_VALUE.getValueDescriptor()); |
} else { |
throw new IllegalStateException("Unexpected json data: " + json); |
} |
} |
- |
+ |
private void mergeWrapper(JsonElement json, Message.Builder builder) |
throws InvalidProtocolBufferException { |
Descriptor type = builder.getDescriptorForType(); |
FieldDescriptor field = type.findFieldByName("value"); |
if (field == null) { |
- throw new InvalidProtocolBufferException( |
- "Invalid wrapper type: " + type.getFullName()); |
+ throw new InvalidProtocolBufferException("Invalid wrapper type: " + type.getFullName()); |
} |
builder.setField(field, parseFieldValue(field, json, builder)); |
} |
- |
- private void mergeField(FieldDescriptor field, JsonElement json, |
- Message.Builder builder) throws InvalidProtocolBufferException { |
+ |
+ private void mergeField(FieldDescriptor field, JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
if (field.isRepeated()) { |
if (builder.getRepeatedFieldCount(field) > 0) { |
throw new InvalidProtocolBufferException( |
@@ -1290,8 +1387,11 @@ public class JsonFormat { |
&& builder.getOneofFieldDescriptor(field.getContainingOneof()) != null) { |
FieldDescriptor other = builder.getOneofFieldDescriptor(field.getContainingOneof()); |
throw new InvalidProtocolBufferException( |
- "Cannot set field " + field.getFullName() + " because another field " |
- + other.getFullName() + " belonging to the same oneof has already been set "); |
+ "Cannot set field " |
+ + field.getFullName() |
+ + " because another field " |
+ + other.getFullName() |
+ + " belonging to the same oneof has already been set "); |
} |
} |
if (field.isRepeated() && json instanceof JsonNull) { |
@@ -1310,44 +1410,38 @@ public class JsonFormat { |
} |
} |
} |
- |
- private void mergeMapField(FieldDescriptor field, JsonElement json, |
- Message.Builder builder) throws InvalidProtocolBufferException { |
+ |
+ private void mergeMapField(FieldDescriptor field, JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
if (!(json instanceof JsonObject)) { |
- throw new InvalidProtocolBufferException( |
- "Expect a map object but found: " + json); |
+ throw new InvalidProtocolBufferException("Expect a map object but found: " + json); |
} |
Descriptor type = field.getMessageType(); |
FieldDescriptor keyField = type.findFieldByName("key"); |
FieldDescriptor valueField = type.findFieldByName("value"); |
if (keyField == null || valueField == null) { |
- throw new InvalidProtocolBufferException( |
- "Invalid map field: " + field.getFullName()); |
+ throw new InvalidProtocolBufferException("Invalid map field: " + field.getFullName()); |
} |
JsonObject object = (JsonObject) json; |
for (Map.Entry<String, JsonElement> entry : object.entrySet()) { |
Message.Builder entryBuilder = builder.newBuilderForField(field); |
- Object key = parseFieldValue( |
- keyField, new JsonPrimitive(entry.getKey()), entryBuilder); |
- Object value = parseFieldValue( |
- valueField, entry.getValue(), entryBuilder); |
+ Object key = parseFieldValue(keyField, new JsonPrimitive(entry.getKey()), entryBuilder); |
+ Object value = parseFieldValue(valueField, entry.getValue(), entryBuilder); |
if (value == null) { |
- throw new InvalidProtocolBufferException( |
- "Map value cannot be null."); |
+ throw new InvalidProtocolBufferException("Map value cannot be null."); |
} |
entryBuilder.setField(keyField, key); |
entryBuilder.setField(valueField, value); |
builder.addRepeatedField(field, entryBuilder.build()); |
} |
} |
- |
+ |
/** |
* 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. |
+ * proto "default" option. |
*/ |
- private Object getDefaultValue(FieldDescriptor field, |
- Message.Builder builder) { |
+ private Object getDefaultValue(FieldDescriptor field, Message.Builder builder) { |
switch (field.getType()) { |
case INT32: |
case SINT32: |
@@ -1377,30 +1471,27 @@ public class JsonFormat { |
case GROUP: |
return builder.newBuilderForField(field).getDefaultInstanceForType(); |
default: |
- throw new IllegalStateException( |
- "Invalid field type: " + field.getType()); |
+ throw new IllegalStateException("Invalid field type: " + field.getType()); |
} |
} |
- |
- private void mergeRepeatedField(FieldDescriptor field, JsonElement json, |
- Message.Builder builder) throws InvalidProtocolBufferException { |
+ |
+ private void mergeRepeatedField( |
+ FieldDescriptor field, JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
if (!(json instanceof JsonArray)) { |
- throw new InvalidProtocolBufferException( |
- "Expect an array but found: " + json); |
+ throw new InvalidProtocolBufferException("Expect an array but found: " + json); |
} |
JsonArray array = (JsonArray) json; |
for (int i = 0; i < array.size(); ++i) { |
Object value = parseFieldValue(field, array.get(i), builder); |
if (value == null) { |
- throw new InvalidProtocolBufferException( |
- "Repeated field elements cannot be null"); |
+ throw new InvalidProtocolBufferException("Repeated field elements cannot be null"); |
} |
builder.addRepeatedField(field, value); |
} |
} |
- |
- private int parseInt32(JsonElement json) |
- throws InvalidProtocolBufferException { |
+ |
+ private int parseInt32(JsonElement json) throws InvalidProtocolBufferException { |
try { |
return Integer.parseInt(json.getAsString()); |
} catch (Exception e) { |
@@ -1416,9 +1507,8 @@ public class JsonFormat { |
throw new InvalidProtocolBufferException("Not an int32 value: " + json); |
} |
} |
- |
- private long parseInt64(JsonElement json) |
- throws InvalidProtocolBufferException { |
+ |
+ private long parseInt64(JsonElement json) throws InvalidProtocolBufferException { |
try { |
return Long.parseLong(json.getAsString()); |
} catch (Exception e) { |
@@ -1434,14 +1524,12 @@ public class JsonFormat { |
throw new InvalidProtocolBufferException("Not an int32 value: " + json); |
} |
} |
- |
- private int parseUint32(JsonElement json) |
- throws InvalidProtocolBufferException { |
+ |
+ private int parseUint32(JsonElement json) throws InvalidProtocolBufferException { |
try { |
long result = Long.parseLong(json.getAsString()); |
if (result < 0 || result > 0xFFFFFFFFL) { |
- throw new InvalidProtocolBufferException( |
- "Out of range uint32 value: " + json); |
+ throw new InvalidProtocolBufferException("Out of range uint32 value: " + json); |
} |
return (int) result; |
} catch (InvalidProtocolBufferException e) { |
@@ -1462,35 +1550,28 @@ public class JsonFormat { |
} catch (InvalidProtocolBufferException e) { |
throw e; |
} catch (Exception e) { |
- throw new InvalidProtocolBufferException( |
- "Not an uint32 value: " + json); |
+ throw new InvalidProtocolBufferException("Not an uint32 value: " + json); |
} |
} |
- |
- private static final BigInteger MAX_UINT64 = |
- new BigInteger("FFFFFFFFFFFFFFFF", 16); |
- |
- private long parseUint64(JsonElement json) |
- throws InvalidProtocolBufferException { |
+ |
+ private static final BigInteger MAX_UINT64 = new BigInteger("FFFFFFFFFFFFFFFF", 16); |
+ |
+ private long parseUint64(JsonElement json) throws InvalidProtocolBufferException { |
try { |
BigDecimal decimalValue = new BigDecimal(json.getAsString()); |
BigInteger value = decimalValue.toBigIntegerExact(); |
- if (value.compareTo(BigInteger.ZERO) < 0 |
- || value.compareTo(MAX_UINT64) > 0) { |
- throw new InvalidProtocolBufferException( |
- "Out of range uint64 value: " + json); |
+ if (value.compareTo(BigInteger.ZERO) < 0 || value.compareTo(MAX_UINT64) > 0) { |
+ throw new InvalidProtocolBufferException("Out of range uint64 value: " + json); |
} |
return value.longValue(); |
} catch (InvalidProtocolBufferException e) { |
throw e; |
} catch (Exception e) { |
- throw new InvalidProtocolBufferException( |
- "Not an uint64 value: " + json); |
+ throw new InvalidProtocolBufferException("Not an uint64 value: " + json); |
} |
} |
- |
- private boolean parseBool(JsonElement json) |
- throws InvalidProtocolBufferException { |
+ |
+ private boolean parseBool(JsonElement json) throws InvalidProtocolBufferException { |
if (json.getAsString().equals("true")) { |
return true; |
} |
@@ -1499,11 +1580,10 @@ public class JsonFormat { |
} |
throw new InvalidProtocolBufferException("Invalid bool value: " + json); |
} |
- |
+ |
private static final double EPSILON = 1e-6; |
- |
- private float parseFloat(JsonElement json) |
- throws InvalidProtocolBufferException { |
+ |
+ private float parseFloat(JsonElement json) throws InvalidProtocolBufferException { |
if (json.getAsString().equals("NaN")) { |
return Float.NaN; |
} else if (json.getAsString().equals("Infinity")) { |
@@ -1521,8 +1601,7 @@ public class JsonFormat { |
// of tolerance when checking whether the float value is in range. |
if (value > Float.MAX_VALUE * (1.0 + EPSILON) |
|| value < -Float.MAX_VALUE * (1.0 + EPSILON)) { |
- throw new InvalidProtocolBufferException( |
- "Out of range float value: " + json); |
+ throw new InvalidProtocolBufferException("Out of range float value: " + json); |
} |
return (float) value; |
} catch (InvalidProtocolBufferException e) { |
@@ -1531,19 +1610,17 @@ public class JsonFormat { |
throw new InvalidProtocolBufferException("Not a float value: " + json); |
} |
} |
- |
- private static final BigDecimal MORE_THAN_ONE = new BigDecimal( |
- String.valueOf(1.0 + EPSILON)); |
+ |
+ private static final BigDecimal MORE_THAN_ONE = new BigDecimal(String.valueOf(1.0 + EPSILON)); |
// When a float value is printed, the printed value might be a little |
// larger or smaller due to precision loss. Here we need to add a bit |
// of tolerance when checking whether the float value is in range. |
- private static final BigDecimal MAX_DOUBLE = new BigDecimal( |
- String.valueOf(Double.MAX_VALUE)).multiply(MORE_THAN_ONE); |
- private static final BigDecimal MIN_DOUBLE = new BigDecimal( |
- String.valueOf(-Double.MAX_VALUE)).multiply(MORE_THAN_ONE); |
- |
- private double parseDouble(JsonElement json) |
- throws InvalidProtocolBufferException { |
+ private static final BigDecimal MAX_DOUBLE = |
+ new BigDecimal(String.valueOf(Double.MAX_VALUE)).multiply(MORE_THAN_ONE); |
+ private static final BigDecimal MIN_DOUBLE = |
+ new BigDecimal(String.valueOf(-Double.MAX_VALUE)).multiply(MORE_THAN_ONE); |
+ |
+ private double parseDouble(JsonElement json) throws InvalidProtocolBufferException { |
if (json.getAsString().equals("NaN")) { |
return Double.NaN; |
} else if (json.getAsString().equals("Infinity")) { |
@@ -1556,36 +1633,27 @@ public class JsonFormat { |
// accepts all values. Here we parse the value into a BigDecimal and do |
// explicit range check on it. |
BigDecimal value = new BigDecimal(json.getAsString()); |
- if (value.compareTo(MAX_DOUBLE) > 0 |
- || value.compareTo(MIN_DOUBLE) < 0) { |
- throw new InvalidProtocolBufferException( |
- "Out of range double value: " + json); |
+ if (value.compareTo(MAX_DOUBLE) > 0 || value.compareTo(MIN_DOUBLE) < 0) { |
+ throw new InvalidProtocolBufferException("Out of range double value: " + json); |
} |
return value.doubleValue(); |
} catch (InvalidProtocolBufferException e) { |
throw e; |
} catch (Exception e) { |
- throw new InvalidProtocolBufferException( |
- "Not an double value: " + json); |
+ throw new InvalidProtocolBufferException("Not an double value: " + json); |
} |
} |
- |
+ |
private String parseString(JsonElement json) { |
return json.getAsString(); |
} |
- |
+ |
private ByteString parseBytes(JsonElement json) throws InvalidProtocolBufferException { |
- String encoded = json.getAsString(); |
- if (encoded.length() % 4 != 0) { |
- throw new InvalidProtocolBufferException( |
- "Bytes field is not encoded in standard BASE64 with paddings: " + encoded); |
- } |
- return ByteString.copyFrom( |
- BaseEncoding.base64().decode(json.getAsString())); |
+ return ByteString.copyFrom(BaseEncoding.base64().decode(json.getAsString())); |
} |
- |
- private EnumValueDescriptor parseEnum(EnumDescriptor enumDescriptor, |
- JsonElement json) throws InvalidProtocolBufferException { |
+ |
+ private EnumValueDescriptor parseEnum(EnumDescriptor enumDescriptor, JsonElement json) |
+ throws InvalidProtocolBufferException { |
String value = json.getAsString(); |
EnumValueDescriptor result = enumDescriptor.findValueByName(value); |
if (result == null) { |
@@ -1602,27 +1670,28 @@ public class JsonFormat { |
// that's not the exception we want the user to see. Since result == null, we will throw |
// an exception later. |
} |
- |
+ |
if (result == null) { |
throw new InvalidProtocolBufferException( |
- "Invalid enum value: " + value + " for enum type: " |
- + enumDescriptor.getFullName()); |
+ "Invalid enum value: " + value + " for enum type: " + enumDescriptor.getFullName()); |
} |
} |
return result; |
} |
- |
- private Object parseFieldValue(FieldDescriptor field, JsonElement json, |
- Message.Builder builder) throws InvalidProtocolBufferException { |
+ |
+ private Object parseFieldValue(FieldDescriptor field, JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
if (json instanceof JsonNull) { |
if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE |
- && field.getMessageType().getFullName().equals( |
- Value.getDescriptor().getFullName())) { |
+ && field.getMessageType().getFullName().equals(Value.getDescriptor().getFullName())) { |
// For every other type, "null" means absence, but for the special |
// Value message, it means the "null_value" field has been set. |
Value value = Value.newBuilder().setNullValueValue(0).build(); |
- return builder.newBuilderForField(field).mergeFrom( |
- value.toByteString()).build(); |
+ return builder.newBuilderForField(field).mergeFrom(value.toByteString()).build(); |
+ } else if (field.getJavaType() == FieldDescriptor.JavaType.ENUM |
+ && field.getEnumType().getFullName().equals(NullValue.getDescriptor().getFullName())) { |
+ // If the type of the field is a NullValue, then the value should be explicitly set. |
+ return field.getEnumType().findValueByNumber(0); |
} |
return null; |
} |
@@ -1642,7 +1711,7 @@ public class JsonFormat { |
case FLOAT: |
return parseFloat(json); |
- |
+ |
case DOUBLE: |
return parseDouble(json); |
@@ -1665,14 +1734,18 @@ public class JsonFormat { |
case MESSAGE: |
case GROUP: |
+ if (currentDepth >= recursionLimit) { |
+ throw new InvalidProtocolBufferException("Hit recursion limit."); |
+ } |
+ ++currentDepth; |
Message.Builder subBuilder = builder.newBuilderForField(field); |
merge(json, subBuilder); |
+ --currentDepth; |
return subBuilder.build(); |
- |
+ |
default: |
- throw new InvalidProtocolBufferException( |
- "Invalid field type: " + field.getType()); |
- } |
+ throw new InvalidProtocolBufferException("Invalid field type: " + field.getType()); |
+ } |
} |
} |
} |