com.google.protobuf.util.JsonFormat Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of protobuf-java-util Show documentation
Show all versions of protobuf-java-util Show documentation
Utilities for Protocol Buffers
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.google.protobuf.util;
import com.google.common.base.Preconditions;
import com.google.common.io.BaseEncoding;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonIOException;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.stream.JsonReader;
import com.google.protobuf.Any;
import com.google.protobuf.BoolValue;
import com.google.protobuf.ByteString;
import com.google.protobuf.BytesValue;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor.Type;
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;
import com.google.protobuf.FieldMask;
import com.google.protobuf.FloatValue;
import com.google.protobuf.Int32Value;
import com.google.protobuf.Int64Value;
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;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.ParseException;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Logger;
/**
* Utility classes to convert protobuf messages to/from JSON format. The JSON
* format follows Proto3 JSON specification and only proto3 features are
* supported. Proto2 only features (e.g., extensions and unknown fields) will
* be discarded in the conversion. That is, when converting proto2 messages
* to JSON format, extensions and unknown fields will be treated as if they
* do not exist. This applies to proto2 messages embedded in proto3 messages
* as well.
*/
public class JsonFormat {
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, Collections.emptySet(),
false, false, false, false);
}
/**
* A Printer converts protobuf message to JSON format.
*/
public static class Printer {
private final TypeRegistry registry;
// NOTE: There are 3 states for these *defaultValueFields variables:
// 1) Default - alwaysOutput is false & including is empty set. Fields only output if they are
// set to non-default values.
// 2) No-args includingDefaultValueFields() called - alwaysOutput is true & including is
// irrelevant (but set to empty set). All fields are output regardless of their values.
// 3) includingDefaultValueFields(Set) called - alwaysOutput is false &
// including is set to the specified set. Fields in that set are always output & fields not
// in that set are only output if set to non-default values.
private boolean alwaysOutputDefaultValueFields;
private Set includingDefaultValueFields;
private final boolean preservingProtoFieldNames;
private final boolean omittingInsignificantWhitespace;
private final boolean printingEnumsAsInts;
private final boolean sortingMapKeys;
private Printer(
TypeRegistry registry,
boolean alwaysOutputDefaultValueFields,
Set includingDefaultValueFields,
boolean preservingProtoFieldNames,
boolean omittingInsignificantWhitespace,
boolean printingEnumsAsInts,
boolean sortingMapKeys) {
this.registry = registry;
this.alwaysOutputDefaultValueFields = alwaysOutputDefaultValueFields;
this.includingDefaultValueFields = includingDefaultValueFields;
this.preservingProtoFieldNames = preservingProtoFieldNames;
this.omittingInsignificantWhitespace = omittingInsignificantWhitespace;
this.printingEnumsAsInts = printingEnumsAsInts;
this.sortingMapKeys = sortingMapKeys;
}
/**
* 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,
alwaysOutputDefaultValueFields,
includingDefaultValueFields,
preservingProtoFieldNames,
omittingInsignificantWhitespace,
printingEnumsAsInts,
sortingMapKeys);
}
/**
* Creates a new {@link Printer} that will also print fields set to their
* defaults. Empty repeated fields and map fields will be printed as well.
* The new Printer clones all other configurations from the current
* {@link Printer}.
*/
public Printer includingDefaultValueFields() {
checkUnsetIncludingDefaultValueFields();
return new Printer(
registry,
true,
Collections.emptySet(),
preservingProtoFieldNames,
omittingInsignificantWhitespace,
printingEnumsAsInts,
sortingMapKeys);
}
/**
* Creates a new {@link Printer} that will print enum field values as integers instead of as
* string.
* The new Printer clones all other configurations from the current
* {@link Printer}.
*/
public Printer printingEnumsAsInts() {
checkUnsetPrintingEnumsAsInts();
return new Printer(
registry,
alwaysOutputDefaultValueFields,
Collections.emptySet(),
preservingProtoFieldNames,
omittingInsignificantWhitespace,
true,
sortingMapKeys);
}
private void checkUnsetPrintingEnumsAsInts() {
if (printingEnumsAsInts) {
throw new IllegalStateException("JsonFormat printingEnumsAsInts has already been set.");
}
}
/**
* Creates a new {@link Printer} that will also print default-valued fields if their
* FieldDescriptors are found in the supplied set. Empty repeated fields and map fields will be
* printed as well, if they match. The new Printer clones all other configurations from the
* current {@link Printer}. Call includingDefaultValueFields() with no args to unconditionally
* output all fields.
*/
public Printer includingDefaultValueFields(Set fieldsToAlwaysOutput) {
Preconditions.checkArgument(
null != fieldsToAlwaysOutput && !fieldsToAlwaysOutput.isEmpty(),
"Non-empty Set must be supplied for includingDefaultValueFields.");
checkUnsetIncludingDefaultValueFields();
return new Printer(
registry,
false,
Collections.unmodifiableSet(new HashSet<>(fieldsToAlwaysOutput)),
preservingProtoFieldNames,
omittingInsignificantWhitespace,
printingEnumsAsInts,
sortingMapKeys);
}
private void checkUnsetIncludingDefaultValueFields() {
if (alwaysOutputDefaultValueFields || !includingDefaultValueFields.isEmpty()) {
throw new IllegalStateException(
"JsonFormat includingDefaultValueFields has already been set.");
}
}
/**
* Creates a new {@link Printer} that is configured to use the original proto
* field names as defined in the .proto file rather than converting them to
* lowerCamelCase. The new Printer clones all other configurations from the
* current {@link Printer}.
*/
public Printer preservingProtoFieldNames() {
return new Printer(
registry,
alwaysOutputDefaultValueFields,
includingDefaultValueFields,
true,
omittingInsignificantWhitespace,
printingEnumsAsInts,
sortingMapKeys);
}
/**
* 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,
alwaysOutputDefaultValueFields,
includingDefaultValueFields,
preservingProtoFieldNames,
true,
printingEnumsAsInts,
sortingMapKeys);
}
/**
* Create a new {@link Printer} that will sort the map keys in the JSON output.
*
* Use of this modifier is discouraged, the generated JSON messages are equivalent
* with and without this option set, but there are some corner caseuse cases that
* demand a stable output, while order of map keys is otherwise arbitrary.
*
* The generated order is not well-defined and should not be depended on, but
* it's stable.
*
* This new Printer clones all other configurations from the current {@link Printer}.
*/
public Printer sortingMapKeys() {
return new Printer(
registry,
alwaysOutputDefaultValueFields,
includingDefaultValueFields,
preservingProtoFieldNames,
omittingInsignificantWhitespace,
printingEnumsAsInts,
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 {
// TODO(xiaofeng): Investigate the allocation overhead and optimize for
// mobile.
new PrinterImpl(
registry,
alwaysOutputDefaultValueFields,
includingDefaultValueFields,
preservingProtoFieldNames,
output,
omittingInsignificantWhitespace,
printingEnumsAsInts,
sortingMapKeys)
.print(message);
}
/**
* Converts a protobuf message to JSON format. Throws exceptions if there
* are unknown Any types in the message.
*/
public String print(MessageOrBuilder message) throws InvalidProtocolBufferException {
try {
StringBuilder builder = new StringBuilder();
appendTo(message, builder);
return builder.toString();
} catch (InvalidProtocolBufferException e) {
throw e;
} catch (IOException e) {
// Unexpected IOException.
throw new IllegalStateException(e);
}
}
}
/**
* Creates a {@link Parser} with default configuration.
*/
public static Parser parser() {
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 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, 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 {
// TODO(xiaofeng): Investigate the allocation overhead and optimize for
// mobile.
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 {
// TODO(xiaofeng): Investigate the allocation overhead and optimize for
// mobile.
new ParserImpl(registry, ignoringUnknownFields, recursionLimit).merge(json, builder);
}
// For testing only.
Parser usingRecursionLimit(int recursionLimit) {
return new Parser(registry, ignoringUnknownFields, recursionLimit);
}
}
/**
* A TypeRegistry is used to resolve Any messages in the JSON conversion.
* You must provide a TypeRegistry containing all message types used in
* Any message fields, or the JSON conversion will fail because data
* in Any message fields is unrecognizable. You don't need to supply a
* TypeRegistry if you don't use Any message fields.
*/
public static class TypeRegistry {
private static class EmptyTypeRegistryHolder {
private static final TypeRegistry EMPTY =
new TypeRegistry(Collections.emptyMap());
}
public static TypeRegistry getEmptyTypeRegistry() {
return EmptyTypeRegistryHolder.EMPTY;
}
public static Builder newBuilder() {
return new Builder();
}
/**
* Find a type by its full name. Returns null if it cannot be found in this {@link
* TypeRegistry}.
*/
public Descriptor find(String name) {
return types.get(name);
}
/* @Nullable */
Descriptor getDescriptorForTypeUrl(String typeUrl) throws InvalidProtocolBufferException {
return find(getTypeName(typeUrl));
}
private final Map types;
private TypeRegistry(Map types) {
this.types = types;
}
/** A Builder is used to build {@link TypeRegistry}. */
public static class Builder {
private Builder() {}
/**
* Adds a message type and all types defined in the same .proto file as well as all
* transitively imported .proto files to this {@link Builder}.
*/
@CanIgnoreReturnValue
public Builder add(Descriptor messageType) {
if (types == null) {
throw new IllegalStateException("A TypeRegistry.Builer can only be used once.");
}
addFile(messageType.getFile());
return this;
}
/**
* Adds message types and all types defined in the same .proto file as well as all
* transitively imported .proto files to this {@link Builder}.
*/
@CanIgnoreReturnValue
public Builder add(Iterable messageTypes) {
if (types == null) {
throw new IllegalStateException("A TypeRegistry.Builder can only be used once.");
}
for (Descriptor type : messageTypes) {
addFile(type.getFile());
}
return this;
}
/**
* Builds a {@link TypeRegistry}. This method can only be called once for
* one Builder.
*/
public TypeRegistry build() {
TypeRegistry result = new TypeRegistry(types);
// Make sure the built {@link TypeRegistry} is immutable.
types = null;
return result;
}
private void addFile(FileDescriptor file) {
// Skip the file if it's already added.
if (!files.add(file.getFullName())) {
return;
}
for (FileDescriptor dependency : file.getDependencies()) {
addFile(dependency);
}
for (Descriptor message : file.getMessageTypes()) {
addMessage(message);
}
}
private void addMessage(Descriptor message) {
for (Descriptor nestedType : message.getNestedTypes()) {
addMessage(nestedType);
}
if (types.containsKey(message.getFullName())) {
logger.warning("Type " + message.getFullName() + " is added multiple times.");
return;
}
types.put(message.getFullName(), message);
}
private final Set files = new HashSet();
private Map types = new HashMap();
}
}
/**
* 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 */
@Override
public void indent() {}
/** ignored by compact printer */
@Override
public void outdent() {}
/** Print text to the output stream. */
@Override
public void print(final CharSequence text) throws IOException {
output.append(text);
}
}
/**
* A TextGenerator adds indentation when writing formatted text.
*/
private static final class PrettyTextGenerator implements TextGenerator {
private final Appendable output;
private final StringBuilder indent = new StringBuilder();
private boolean atStartOfLine = true;
private PrettyTextGenerator(final Appendable output) {
this.output = output;
}
/**
* Indent text by two spaces. After calling Indent(), two spaces will be inserted at the
* beginning of each line of text. Indent() may be called multiple times to produce deeper
* indents.
*/
@Override
public void indent() {
indent.append(" ");
}
/** Reduces the current indent level by two spaces, or crashes if the indent level is zero. */
@Override
public void outdent() {
final int length = indent.length();
if (length < 2) {
throw new IllegalArgumentException(" Outdent() without matching Indent().");
}
indent.delete(length - 2, length);
}
/** Print text to the output stream. */
@Override
public void print(final CharSequence text) throws IOException {
final int size = text.length();
int pos = 0;
for (int i = 0; i < size; i++) {
if (text.charAt(i) == '\n') {
write(text.subSequence(pos, i + 1));
pos = i + 1;
atStartOfLine = true;
}
}
write(text.subSequence(pos, size));
}
private void write(final CharSequence data) throws IOException {
if (data.length() == 0) {
return;
}
if (atStartOfLine) {
atStartOfLine = false;
output.append(indent);
}
output.append(data);
}
}
/**
* A Printer converts protobuf messages to JSON format.
*/
private static final class PrinterImpl {
private final TypeRegistry registry;
private final boolean alwaysOutputDefaultValueFields;
private final Set includingDefaultValueFields;
private final boolean preservingProtoFieldNames;
private final boolean printingEnumsAsInts;
private final boolean sortingMapKeys;
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().create();
}
PrinterImpl(
TypeRegistry registry,
boolean alwaysOutputDefaultValueFields,
Set includingDefaultValueFields,
boolean preservingProtoFieldNames,
Appendable jsonOutput,
boolean omittingInsignificantWhitespace,
boolean printingEnumsAsInts,
boolean sortingMapKeys) {
this.registry = registry;
this.alwaysOutputDefaultValueFields = alwaysOutputDefaultValueFields;
this.includingDefaultValueFields = includingDefaultValueFields;
this.preservingProtoFieldNames = preservingProtoFieldNames;
this.printingEnumsAsInts = printingEnumsAsInts;
this.sortingMapKeys = sortingMapKeys;
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());
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 wellKnownTypePrinters =
buildWellKnownTypePrinters();
private static Map buildWellKnownTypePrinters() {
Map printers = new HashMap();
// Special-case Any.
printers.put(
Any.getDescriptor().getFullName(),
new WellKnownTypePrinter() {
@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);
}
};
printers.put(BoolValue.getDescriptor().getFullName(), wrappersPrinter);
printers.put(Int32Value.getDescriptor().getFullName(), wrappersPrinter);
printers.put(UInt32Value.getDescriptor().getFullName(), wrappersPrinter);
printers.put(Int64Value.getDescriptor().getFullName(), wrappersPrinter);
printers.put(UInt64Value.getDescriptor().getFullName(), wrappersPrinter);
printers.put(StringValue.getDescriptor().getFullName(), wrappersPrinter);
printers.put(BytesValue.getDescriptor().getFullName(), wrappersPrinter);
printers.put(FloatValue.getDescriptor().getFullName(), wrappersPrinter);
printers.put(DoubleValue.getDescriptor().getFullName(), wrappersPrinter);
// Special-case Timestamp.
printers.put(
Timestamp.getDescriptor().getFullName(),
new WellKnownTypePrinter() {
@Override
public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException {
printer.printTimestamp(message);
}
});
// Special-case Duration.
printers.put(
Duration.getDescriptor().getFullName(),
new WellKnownTypePrinter() {
@Override
public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException {
printer.printDuration(message);
}
});
// Special-case FieldMask.
printers.put(
FieldMask.getDescriptor().getFullName(),
new WellKnownTypePrinter() {
@Override
public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException {
printer.printFieldMask(message);
}
});
// Special-case Struct.
printers.put(
Struct.getDescriptor().getFullName(),
new WellKnownTypePrinter() {
@Override
public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException {
printer.printStruct(message);
}
});
// Special-case Value.
printers.put(
Value.getDescriptor().getFullName(),
new WellKnownTypePrinter() {
@Override
public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException {
printer.printValue(message);
}
});
// Special-case ListValue.
printers.put(
ListValue.getDescriptor().getFullName(),
new WellKnownTypePrinter() {
@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
|| typeUrlField.getType() != FieldDescriptor.Type.STRING
|| valueField.getType() != FieldDescriptor.Type.BYTES) {
throw new InvalidProtocolBufferException("Invalid Any type.");
}
String typeUrl = (String) message.getField(typeUrlField);
Descriptor type = registry.getDescriptorForTypeUrl(typeUrl);
if (type == null) {
throw new InvalidProtocolBufferException("Cannot find type for url: " + typeUrl);
}
ByteString content = (ByteString) message.getField(valueField);
Message contentMessage =
DynamicMessage.getDefaultInstance(type).getParserForType().parseFrom(content);
WellKnownTypePrinter printer = wellKnownTypePrinters.get(getTypeName(typeUrl));
if (printer != null) {
// If the type is one of the well-known types, we use a special
// formatting.
generator.print("{" + blankOrNewLine);
generator.indent();
generator.print("\"@type\":" + blankOrSpace + gson.toJson(typeUrl) + "," + blankOrNewLine);
generator.print("\"value\":" + blankOrSpace);
printer.print(this, contentMessage);
generator.print(blankOrNewLine);
generator.outdent();
generator.print("}");
} else {
// Print the content message instead (with a "@type" field added).
print(contentMessage, typeUrl);
}
}
/** Prints wrapper types (e.g., google.protobuf.Int32Value) */
private void printWrapper(MessageOrBuilder message) throws IOException {
Descriptor descriptor = message.getDescriptorForType();
FieldDescriptor valueField = descriptor.findFieldByName("value");
if (valueField == null) {
throw new InvalidProtocolBufferException("Invalid Wrapper type.");
}
// When formatting wrapper types, we just print its value field instead of
// the whole message.
printSingleFieldValue(valueField, message.getField(valueField));
}
private ByteString toByteString(MessageOrBuilder message) {
if (message instanceof Message) {
return ((Message) message).toByteString();
} else {
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("\"" + Timestamps.toString(value) + "\"");
}
/** Prints google.protobuf.Duration */
private void printDuration(MessageOrBuilder message) throws IOException {
Duration value = Duration.parseFrom(toByteString(message));
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.toJsonString(value) + "\"");
}
/** Prints google.protobuf.Struct */
private void printStruct(MessageOrBuilder message) throws IOException {
Descriptor descriptor = message.getDescriptorForType();
FieldDescriptor field = descriptor.findFieldByName("fields");
if (field == null) {
throw new InvalidProtocolBufferException("Invalid Struct type.");
}
// 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.
Map fields = message.getAllFields();
if (fields.isEmpty()) {
// No value set.
generator.print("null");
return;
}
// A Value message can only have at most one field set (it only contains
// an oneof).
if (fields.size() != 1) {
throw new InvalidProtocolBufferException("Invalid Value type.");
}
for (Map.Entry entry : fields.entrySet()) {
printSingleFieldValue(entry.getKey(), entry.getValue());
}
}
/** Prints google.protobuf.ListValue */
private void printListValue(MessageOrBuilder message) throws IOException {
Descriptor descriptor = message.getDescriptorForType();
FieldDescriptor field = descriptor.findFieldByName("values");
if (field == null) {
throw new InvalidProtocolBufferException("Invalid ListValue type.");
}
printRepeatedFieldValue(field, message.getField(field));
}
/** Prints a regular message with an optional type URL. */
private void print(MessageOrBuilder message, String typeUrl) throws IOException {
generator.print("{" + blankOrNewLine);
generator.indent();
boolean printedField = false;
if (typeUrl != null) {
generator.print("\"@type\":" + blankOrSpace + gson.toJson(typeUrl));
printedField = true;
}
Map fieldsToPrint = null;
if (alwaysOutputDefaultValueFields || !includingDefaultValueFields.isEmpty()) {
fieldsToPrint = new TreeMap(message.getAllFields());
for (FieldDescriptor field : message.getDescriptorForType().getFields()) {
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;
}
}
if (!fieldsToPrint.containsKey(field)
&& (alwaysOutputDefaultValueFields || includingDefaultValueFields.contains(field))) {
fieldsToPrint.put(field, message.getField(field));
}
}
} else {
fieldsToPrint = message.getAllFields();
}
for (Map.Entry field : fieldsToPrint.entrySet()) {
if (printedField) {
// Add line-endings for the previous field.
generator.print("," + blankOrNewLine);
} else {
printedField = true;
}
printField(field.getKey(), field.getValue());
}
// Add line-endings for the last field.
if (printedField) {
generator.print(blankOrNewLine);
}
generator.outdent();
generator.print("}");
}
private void printField(FieldDescriptor field, Object value) throws IOException {
if (preservingProtoFieldNames) {
generator.print("\"" + field.getName() + "\":" + blankOrSpace);
} else {
generator.print("\"" + field.getJsonName() + "\":" + blankOrSpace);
}
if (field.isMapField()) {
printMapFieldValue(field, value);
} else if (field.isRepeated()) {
printRepeatedFieldValue(field, value);
} else {
printSingleFieldValue(field, value);
}
}
@SuppressWarnings("rawtypes")
private void printRepeatedFieldValue(FieldDescriptor field, Object value) throws IOException {
generator.print("[");
boolean printedElement = false;
for (Object element : (List) value) {
if (printedElement) {
generator.print("," + blankOrSpace);
} else {
printedElement = true;
}
printSingleFieldValue(field, element);
}
generator.print("]");
}
@SuppressWarnings("rawtypes")
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("{" + blankOrNewLine);
generator.indent();
@SuppressWarnings("unchecked") // Object guaranteed to be a List for a map field.
Collection