1 // Protocol Buffers - Google's data interchange format 2 // Copyright 2008 Google Inc. All rights reserved. 3 // https://developers.google.com/protocol-buffers/ 4 // 5 // Redistribution and use in source and binary forms, with or without 6 // modification, are permitted provided that the following conditions are 7 // met: 8 // 9 // * Redistributions of source code must retain the above copyright 10 // notice, this list of conditions and the following disclaimer. 11 // * Redistributions in binary form must reproduce the above 12 // copyright notice, this list of conditions and the following disclaimer 13 // in the documentation and/or other materials provided with the 14 // distribution. 15 // * Neither the name of Google Inc. nor the names of its 16 // contributors may be used to endorse or promote products derived from 17 // this software without specific prior written permission. 18 // 19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31 package com.google.protobuf.util; 32 33 import com.google.common.io.BaseEncoding; 34 import com.google.gson.Gson; 35 import com.google.gson.GsonBuilder; 36 import com.google.gson.JsonArray; 37 import com.google.gson.JsonElement; 38 import com.google.gson.JsonNull; 39 import com.google.gson.JsonObject; 40 import com.google.gson.JsonParser; 41 import com.google.gson.JsonPrimitive; 42 import com.google.gson.stream.JsonReader; 43 import com.google.protobuf.Any; 44 import com.google.protobuf.BoolValue; 45 import com.google.protobuf.ByteString; 46 import com.google.protobuf.BytesValue; 47 import com.google.protobuf.Descriptors.Descriptor; 48 import com.google.protobuf.Descriptors.EnumDescriptor; 49 import com.google.protobuf.Descriptors.EnumValueDescriptor; 50 import com.google.protobuf.Descriptors.FieldDescriptor; 51 import com.google.protobuf.Descriptors.FileDescriptor; 52 import com.google.protobuf.DoubleValue; 53 import com.google.protobuf.Duration; 54 import com.google.protobuf.DynamicMessage; 55 import com.google.protobuf.FieldMask; 56 import com.google.protobuf.FloatValue; 57 import com.google.protobuf.Int32Value; 58 import com.google.protobuf.Int64Value; 59 import com.google.protobuf.InvalidProtocolBufferException; 60 import com.google.protobuf.ListValue; 61 import com.google.protobuf.Message; 62 import com.google.protobuf.MessageOrBuilder; 63 import com.google.protobuf.NullValue; 64 import com.google.protobuf.StringValue; 65 import com.google.protobuf.Struct; 66 import com.google.protobuf.Timestamp; 67 import com.google.protobuf.UInt32Value; 68 import com.google.protobuf.UInt64Value; 69 import com.google.protobuf.Value; 70 71 import java.io.IOException; 72 import java.io.Reader; 73 import java.io.StringReader; 74 import java.math.BigDecimal; 75 import java.math.BigInteger; 76 import java.text.ParseException; 77 import java.util.Collections; 78 import java.util.HashMap; 79 import java.util.HashSet; 80 import java.util.List; 81 import java.util.Map; 82 import java.util.Set; 83 import java.util.TreeMap; 84 import java.util.logging.Logger; 85 86 /** 87 * Utility classes to convert protobuf messages to/from JSON format. The JSON 88 * format follows Proto3 JSON specification and only proto3 features are 89 * supported. Proto2 only features (e.g., extensions and unknown fields) will 90 * be discarded in the conversion. That is, when converting proto2 messages 91 * to JSON format, extensions and unknown fields will be treated as if they 92 * do not exist. This applies to proto2 messages embedded in proto3 messages 93 * as well. 94 */ 95 public class JsonFormat { 96 private static final Logger logger = Logger.getLogger(JsonFormat.class.getName()); 97 JsonFormat()98 private JsonFormat() {} 99 100 /** 101 * Creates a {@link Printer} with default configurations. 102 */ printer()103 public static Printer printer() { 104 return new Printer(TypeRegistry.getEmptyTypeRegistry(), false, false, false); 105 } 106 107 /** 108 * A Printer converts protobuf message to JSON format. 109 */ 110 public static class Printer { 111 private final TypeRegistry registry; 112 private final boolean includingDefaultValueFields; 113 private final boolean preservingProtoFieldNames; 114 private final boolean omittingInsignificantWhitespace; 115 Printer( TypeRegistry registry, boolean includingDefaultValueFields, boolean preservingProtoFieldNames, boolean omittingInsignificantWhitespace)116 private Printer( 117 TypeRegistry registry, 118 boolean includingDefaultValueFields, 119 boolean preservingProtoFieldNames, 120 boolean omittingInsignificantWhitespace) { 121 this.registry = registry; 122 this.includingDefaultValueFields = includingDefaultValueFields; 123 this.preservingProtoFieldNames = preservingProtoFieldNames; 124 this.omittingInsignificantWhitespace = omittingInsignificantWhitespace; 125 } 126 127 /** 128 * Creates a new {@link Printer} using the given registry. The new Printer 129 * clones all other configurations from the current {@link Printer}. 130 * 131 * @throws IllegalArgumentException if a registry is already set. 132 */ usingTypeRegistry(TypeRegistry registry)133 public Printer usingTypeRegistry(TypeRegistry registry) { 134 if (this.registry != TypeRegistry.getEmptyTypeRegistry()) { 135 throw new IllegalArgumentException("Only one registry is allowed."); 136 } 137 return new Printer( 138 registry, 139 includingDefaultValueFields, 140 preservingProtoFieldNames, 141 omittingInsignificantWhitespace); 142 } 143 144 /** 145 * Creates a new {@link Printer} that will also print fields set to their 146 * defaults. Empty repeated fields and map fields will be printed as well. 147 * The new Printer clones all other configurations from the current 148 * {@link Printer}. 149 */ includingDefaultValueFields()150 public Printer includingDefaultValueFields() { 151 return new Printer( 152 registry, true, preservingProtoFieldNames, omittingInsignificantWhitespace); 153 } 154 155 /** 156 * Creates a new {@link Printer} that is configured to use the original proto 157 * field names as defined in the .proto file rather than converting them to 158 * lowerCamelCase. The new Printer clones all other configurations from the 159 * current {@link Printer}. 160 */ preservingProtoFieldNames()161 public Printer preservingProtoFieldNames() { 162 return new Printer( 163 registry, includingDefaultValueFields, true, omittingInsignificantWhitespace); 164 } 165 166 167 /** 168 * Create a new {@link Printer} that will omit all insignificant whitespace 169 * in the JSON output. This new Printer clones all other configurations from the 170 * current Printer. Insignificant whitespace is defined by the JSON spec as whitespace 171 * that appear between JSON structural elements: 172 * <pre> 173 * ws = *( 174 * %x20 / ; Space 175 * %x09 / ; Horizontal tab 176 * %x0A / ; Line feed or New line 177 * %x0D ) ; Carriage return 178 * </pre> 179 * See <a href="https://tools.ietf.org/html/rfc7159">https://tools.ietf.org/html/rfc7159</a> 180 * current {@link Printer}. 181 */ omittingInsignificantWhitespace()182 public Printer omittingInsignificantWhitespace() { 183 return new Printer(registry, includingDefaultValueFields, preservingProtoFieldNames, true); 184 } 185 186 /** 187 * Converts a protobuf message to JSON format. 188 * 189 * @throws InvalidProtocolBufferException if the message contains Any types 190 * that can't be resolved. 191 * @throws IOException if writing to the output fails. 192 */ appendTo(MessageOrBuilder message, Appendable output)193 public void appendTo(MessageOrBuilder message, Appendable output) throws IOException { 194 // TODO(xiaofeng): Investigate the allocation overhead and optimize for 195 // mobile. 196 new PrinterImpl( 197 registry, 198 includingDefaultValueFields, 199 preservingProtoFieldNames, 200 output, 201 omittingInsignificantWhitespace) 202 .print(message); 203 } 204 205 /** 206 * Converts a protobuf message to JSON format. Throws exceptions if there 207 * are unknown Any types in the message. 208 */ print(MessageOrBuilder message)209 public String print(MessageOrBuilder message) throws InvalidProtocolBufferException { 210 try { 211 StringBuilder builder = new StringBuilder(); 212 appendTo(message, builder); 213 return builder.toString(); 214 } catch (InvalidProtocolBufferException e) { 215 throw e; 216 } catch (IOException e) { 217 // Unexpected IOException. 218 throw new IllegalStateException(e); 219 } 220 } 221 } 222 223 /** 224 * Creates a {@link Parser} with default configuration. 225 */ parser()226 public static Parser parser() { 227 return new Parser(TypeRegistry.getEmptyTypeRegistry()); 228 } 229 230 /** 231 * A Parser parses JSON to protobuf message. 232 */ 233 public static class Parser { 234 private final TypeRegistry registry; 235 Parser(TypeRegistry registry)236 private Parser(TypeRegistry registry) { 237 this.registry = registry; 238 } 239 240 /** 241 * Creates a new {@link Parser} using the given registry. The new Parser 242 * clones all other configurations from this Parser. 243 * 244 * @throws IllegalArgumentException if a registry is already set. 245 */ usingTypeRegistry(TypeRegistry registry)246 public Parser usingTypeRegistry(TypeRegistry registry) { 247 if (this.registry != TypeRegistry.getEmptyTypeRegistry()) { 248 throw new IllegalArgumentException("Only one registry is allowed."); 249 } 250 return new Parser(registry); 251 } 252 253 /** 254 * Parses from JSON into a protobuf message. 255 * 256 * @throws InvalidProtocolBufferException if the input is not valid JSON 257 * format or there are unknown fields in the input. 258 */ merge(String json, Message.Builder builder)259 public void merge(String json, Message.Builder builder) throws InvalidProtocolBufferException { 260 // TODO(xiaofeng): Investigate the allocation overhead and optimize for 261 // mobile. 262 new ParserImpl(registry).merge(json, builder); 263 } 264 265 /** 266 * Parses from JSON into a protobuf message. 267 * 268 * @throws InvalidProtocolBufferException if the input is not valid JSON 269 * format or there are unknown fields in the input. 270 * @throws IOException if reading from the input throws. 271 */ merge(Reader json, Message.Builder builder)272 public void merge(Reader json, Message.Builder builder) throws IOException { 273 // TODO(xiaofeng): Investigate the allocation overhead and optimize for 274 // mobile. 275 new ParserImpl(registry).merge(json, builder); 276 } 277 } 278 279 /** 280 * A TypeRegistry is used to resolve Any messages in the JSON conversion. 281 * You must provide a TypeRegistry containing all message types used in 282 * Any message fields, or the JSON conversion will fail because data 283 * in Any message fields is unrecognizable. You don't need to supply a 284 * TypeRegistry if you don't use Any message fields. 285 */ 286 public static class TypeRegistry { 287 private static class EmptyTypeRegistryHolder { 288 private static final TypeRegistry EMPTY = 289 new TypeRegistry(Collections.<String, Descriptor>emptyMap()); 290 } 291 getEmptyTypeRegistry()292 public static TypeRegistry getEmptyTypeRegistry() { 293 return EmptyTypeRegistryHolder.EMPTY; 294 } 295 newBuilder()296 public static Builder newBuilder() { 297 return new Builder(); 298 } 299 300 /** 301 * Find a type by its full name. Returns null if it cannot be found in 302 * this {@link TypeRegistry}. 303 */ find(String name)304 public Descriptor find(String name) { 305 return types.get(name); 306 } 307 308 private final Map<String, Descriptor> types; 309 TypeRegistry(Map<String, Descriptor> types)310 private TypeRegistry(Map<String, Descriptor> types) { 311 this.types = types; 312 } 313 314 /** 315 * A Builder is used to build {@link TypeRegistry}. 316 */ 317 public static class Builder { Builder()318 private Builder() {} 319 320 /** 321 * Adds a message type and all types defined in the same .proto file as 322 * well as all transitively imported .proto files to this {@link Builder}. 323 */ add(Descriptor messageType)324 public Builder add(Descriptor messageType) { 325 if (types == null) { 326 throw new IllegalStateException("A TypeRegistry.Builer can only be used once."); 327 } 328 addFile(messageType.getFile()); 329 return this; 330 } 331 332 /** 333 * Adds message types and all types defined in the same .proto file as 334 * well as all transitively imported .proto files to this {@link Builder}. 335 */ add(Iterable<Descriptor> messageTypes)336 public Builder add(Iterable<Descriptor> messageTypes) { 337 if (types == null) { 338 throw new IllegalStateException("A TypeRegistry.Builer can only be used once."); 339 } 340 for (Descriptor type : messageTypes) { 341 addFile(type.getFile()); 342 } 343 return this; 344 } 345 346 /** 347 * Builds a {@link TypeRegistry}. This method can only be called once for 348 * one Builder. 349 */ build()350 public TypeRegistry build() { 351 TypeRegistry result = new TypeRegistry(types); 352 // Make sure the built {@link TypeRegistry} is immutable. 353 types = null; 354 return result; 355 } 356 addFile(FileDescriptor file)357 private void addFile(FileDescriptor file) { 358 // Skip the file if it's already added. 359 if (!files.add(file.getFullName())) { 360 return; 361 } 362 for (FileDescriptor dependency : file.getDependencies()) { 363 addFile(dependency); 364 } 365 for (Descriptor message : file.getMessageTypes()) { 366 addMessage(message); 367 } 368 } 369 addMessage(Descriptor message)370 private void addMessage(Descriptor message) { 371 for (Descriptor nestedType : message.getNestedTypes()) { 372 addMessage(nestedType); 373 } 374 375 if (types.containsKey(message.getFullName())) { 376 logger.warning("Type " + message.getFullName() + " is added multiple times."); 377 return; 378 } 379 380 types.put(message.getFullName(), message); 381 } 382 383 private final Set<String> files = new HashSet<String>(); 384 private Map<String, Descriptor> types = new HashMap<String, Descriptor>(); 385 } 386 } 387 388 /** 389 * An interface for json formatting that can be used in 390 * combination with the omittingInsignificantWhitespace() method 391 */ 392 interface TextGenerator { indent()393 void indent(); 394 outdent()395 void outdent(); 396 print(final CharSequence text)397 void print(final CharSequence text) throws IOException; 398 } 399 400 /** 401 * Format the json without indentation 402 */ 403 private static final class CompactTextGenerator implements TextGenerator { 404 private final Appendable output; 405 CompactTextGenerator(final Appendable output)406 private CompactTextGenerator(final Appendable output) { 407 this.output = output; 408 } 409 410 /** 411 * ignored by compact printer 412 */ indent()413 public void indent() {} 414 415 /** 416 * ignored by compact printer 417 */ outdent()418 public void outdent() {} 419 420 /** 421 * Print text to the output stream. 422 */ print(final CharSequence text)423 public void print(final CharSequence text) throws IOException { 424 output.append(text); 425 } 426 } 427 /** 428 * A TextGenerator adds indentation when writing formatted text. 429 */ 430 private static final class PrettyTextGenerator implements TextGenerator { 431 private final Appendable output; 432 private final StringBuilder indent = new StringBuilder(); 433 private boolean atStartOfLine = true; 434 PrettyTextGenerator(final Appendable output)435 private PrettyTextGenerator(final Appendable output) { 436 this.output = output; 437 } 438 439 /** 440 * Indent text by two spaces. After calling Indent(), two spaces will be 441 * inserted at the beginning of each line of text. Indent() may be called 442 * multiple times to produce deeper indents. 443 */ indent()444 public void indent() { 445 indent.append(" "); 446 } 447 448 /** 449 * Reduces the current indent level by two spaces, or crashes if the indent 450 * level is zero. 451 */ outdent()452 public void outdent() { 453 final int length = indent.length(); 454 if (length < 2) { 455 throw new IllegalArgumentException(" Outdent() without matching Indent()."); 456 } 457 indent.delete(length - 2, length); 458 } 459 460 /** 461 * Print text to the output stream. 462 */ print(final CharSequence text)463 public void print(final CharSequence text) throws IOException { 464 final int size = text.length(); 465 int pos = 0; 466 467 for (int i = 0; i < size; i++) { 468 if (text.charAt(i) == '\n') { 469 write(text.subSequence(pos, i + 1)); 470 pos = i + 1; 471 atStartOfLine = true; 472 } 473 } 474 write(text.subSequence(pos, size)); 475 } 476 write(final CharSequence data)477 private void write(final CharSequence data) throws IOException { 478 if (data.length() == 0) { 479 return; 480 } 481 if (atStartOfLine) { 482 atStartOfLine = false; 483 output.append(indent); 484 } 485 output.append(data); 486 } 487 } 488 489 /** 490 * A Printer converts protobuf messages to JSON format. 491 */ 492 private static final class PrinterImpl { 493 private final TypeRegistry registry; 494 private final boolean includingDefaultValueFields; 495 private final boolean preservingProtoFieldNames; 496 private final TextGenerator generator; 497 // We use Gson to help handle string escapes. 498 private final Gson gson; 499 private final CharSequence blankOrSpace; 500 private final CharSequence blankOrNewLine; 501 502 private static class GsonHolder { 503 private static final Gson DEFAULT_GSON = new GsonBuilder().disableHtmlEscaping().create(); 504 } 505 PrinterImpl( TypeRegistry registry, boolean includingDefaultValueFields, boolean preservingProtoFieldNames, Appendable jsonOutput, boolean omittingInsignificantWhitespace)506 PrinterImpl( 507 TypeRegistry registry, 508 boolean includingDefaultValueFields, 509 boolean preservingProtoFieldNames, 510 Appendable jsonOutput, 511 boolean omittingInsignificantWhitespace) { 512 this.registry = registry; 513 this.includingDefaultValueFields = includingDefaultValueFields; 514 this.preservingProtoFieldNames = preservingProtoFieldNames; 515 this.gson = GsonHolder.DEFAULT_GSON; 516 // json format related properties, determined by printerType 517 if (omittingInsignificantWhitespace) { 518 this.generator = new CompactTextGenerator(jsonOutput); 519 this.blankOrSpace = ""; 520 this.blankOrNewLine = ""; 521 } else { 522 this.generator = new PrettyTextGenerator(jsonOutput); 523 this.blankOrSpace = " "; 524 this.blankOrNewLine = "\n"; 525 } 526 } 527 print(MessageOrBuilder message)528 void print(MessageOrBuilder message) throws IOException { 529 WellKnownTypePrinter specialPrinter = 530 wellKnownTypePrinters.get(message.getDescriptorForType().getFullName()); 531 if (specialPrinter != null) { 532 specialPrinter.print(this, message); 533 return; 534 } 535 print(message, null); 536 } 537 538 private interface WellKnownTypePrinter { print(PrinterImpl printer, MessageOrBuilder message)539 void print(PrinterImpl printer, MessageOrBuilder message) throws IOException; 540 } 541 542 private static final Map<String, WellKnownTypePrinter> wellKnownTypePrinters = 543 buildWellKnownTypePrinters(); 544 buildWellKnownTypePrinters()545 private static Map<String, WellKnownTypePrinter> buildWellKnownTypePrinters() { 546 Map<String, WellKnownTypePrinter> printers = new HashMap<String, WellKnownTypePrinter>(); 547 // Special-case Any. 548 printers.put( 549 Any.getDescriptor().getFullName(), 550 new WellKnownTypePrinter() { 551 @Override 552 public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { 553 printer.printAny(message); 554 } 555 }); 556 // Special-case wrapper types. 557 WellKnownTypePrinter wrappersPrinter = 558 new WellKnownTypePrinter() { 559 @Override 560 public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { 561 printer.printWrapper(message); 562 } 563 }; 564 printers.put(BoolValue.getDescriptor().getFullName(), wrappersPrinter); 565 printers.put(Int32Value.getDescriptor().getFullName(), wrappersPrinter); 566 printers.put(UInt32Value.getDescriptor().getFullName(), wrappersPrinter); 567 printers.put(Int64Value.getDescriptor().getFullName(), wrappersPrinter); 568 printers.put(UInt64Value.getDescriptor().getFullName(), wrappersPrinter); 569 printers.put(StringValue.getDescriptor().getFullName(), wrappersPrinter); 570 printers.put(BytesValue.getDescriptor().getFullName(), wrappersPrinter); 571 printers.put(FloatValue.getDescriptor().getFullName(), wrappersPrinter); 572 printers.put(DoubleValue.getDescriptor().getFullName(), wrappersPrinter); 573 // Special-case Timestamp. 574 printers.put( 575 Timestamp.getDescriptor().getFullName(), 576 new WellKnownTypePrinter() { 577 @Override 578 public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { 579 printer.printTimestamp(message); 580 } 581 }); 582 // Special-case Duration. 583 printers.put( 584 Duration.getDescriptor().getFullName(), 585 new WellKnownTypePrinter() { 586 @Override 587 public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { 588 printer.printDuration(message); 589 } 590 }); 591 // Special-case FieldMask. 592 printers.put( 593 FieldMask.getDescriptor().getFullName(), 594 new WellKnownTypePrinter() { 595 @Override 596 public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { 597 printer.printFieldMask(message); 598 } 599 }); 600 // Special-case Struct. 601 printers.put( 602 Struct.getDescriptor().getFullName(), 603 new WellKnownTypePrinter() { 604 @Override 605 public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { 606 printer.printStruct(message); 607 } 608 }); 609 // Special-case Value. 610 printers.put( 611 Value.getDescriptor().getFullName(), 612 new WellKnownTypePrinter() { 613 @Override 614 public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { 615 printer.printValue(message); 616 } 617 }); 618 // Special-case ListValue. 619 printers.put( 620 ListValue.getDescriptor().getFullName(), 621 new WellKnownTypePrinter() { 622 @Override 623 public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { 624 printer.printListValue(message); 625 } 626 }); 627 return printers; 628 } 629 630 /** Prints google.protobuf.Any */ printAny(MessageOrBuilder message)631 private void printAny(MessageOrBuilder message) throws IOException { 632 Descriptor descriptor = message.getDescriptorForType(); 633 FieldDescriptor typeUrlField = descriptor.findFieldByName("type_url"); 634 FieldDescriptor valueField = descriptor.findFieldByName("value"); 635 // Validates type of the message. Note that we can't just cast the message 636 // to com.google.protobuf.Any because it might be a DynamicMessage. 637 if (typeUrlField == null 638 || valueField == null 639 || typeUrlField.getType() != FieldDescriptor.Type.STRING 640 || valueField.getType() != FieldDescriptor.Type.BYTES) { 641 throw new InvalidProtocolBufferException("Invalid Any type."); 642 } 643 String typeUrl = (String) message.getField(typeUrlField); 644 String typeName = getTypeName(typeUrl); 645 Descriptor type = registry.find(typeName); 646 if (type == null) { 647 throw new InvalidProtocolBufferException("Cannot find type for url: " + typeUrl); 648 } 649 ByteString content = (ByteString) message.getField(valueField); 650 Message contentMessage = 651 DynamicMessage.getDefaultInstance(type).getParserForType().parseFrom(content); 652 WellKnownTypePrinter printer = wellKnownTypePrinters.get(typeName); 653 if (printer != null) { 654 // If the type is one of the well-known types, we use a special 655 // formatting. 656 generator.print("{" + blankOrNewLine); 657 generator.indent(); 658 generator.print("\"@type\":" + blankOrSpace + gson.toJson(typeUrl) + "," + blankOrNewLine); 659 generator.print("\"value\":" + blankOrSpace); 660 printer.print(this, contentMessage); 661 generator.print(blankOrNewLine); 662 generator.outdent(); 663 generator.print("}"); 664 } else { 665 // Print the content message instead (with a "@type" field added). 666 print(contentMessage, typeUrl); 667 } 668 } 669 670 /** Prints wrapper types (e.g., google.protobuf.Int32Value) */ printWrapper(MessageOrBuilder message)671 private void printWrapper(MessageOrBuilder message) throws IOException { 672 Descriptor descriptor = message.getDescriptorForType(); 673 FieldDescriptor valueField = descriptor.findFieldByName("value"); 674 if (valueField == null) { 675 throw new InvalidProtocolBufferException("Invalid Wrapper type."); 676 } 677 // When formatting wrapper types, we just print its value field instead of 678 // the whole message. 679 printSingleFieldValue(valueField, message.getField(valueField)); 680 } 681 toByteString(MessageOrBuilder message)682 private ByteString toByteString(MessageOrBuilder message) { 683 if (message instanceof Message) { 684 return ((Message) message).toByteString(); 685 } else { 686 return ((Message.Builder) message).build().toByteString(); 687 } 688 } 689 690 /** Prints google.protobuf.Timestamp */ printTimestamp(MessageOrBuilder message)691 private void printTimestamp(MessageOrBuilder message) throws IOException { 692 Timestamp value = Timestamp.parseFrom(toByteString(message)); 693 generator.print("\"" + Timestamps.toString(value) + "\""); 694 } 695 696 /** Prints google.protobuf.Duration */ printDuration(MessageOrBuilder message)697 private void printDuration(MessageOrBuilder message) throws IOException { 698 Duration value = Duration.parseFrom(toByteString(message)); 699 generator.print("\"" + Durations.toString(value) + "\""); 700 } 701 702 /** Prints google.protobuf.FieldMask */ printFieldMask(MessageOrBuilder message)703 private void printFieldMask(MessageOrBuilder message) throws IOException { 704 FieldMask value = FieldMask.parseFrom(toByteString(message)); 705 generator.print("\"" + FieldMaskUtil.toJsonString(value) + "\""); 706 } 707 708 /** Prints google.protobuf.Struct */ printStruct(MessageOrBuilder message)709 private void printStruct(MessageOrBuilder message) throws IOException { 710 Descriptor descriptor = message.getDescriptorForType(); 711 FieldDescriptor field = descriptor.findFieldByName("fields"); 712 if (field == null) { 713 throw new InvalidProtocolBufferException("Invalid Struct type."); 714 } 715 // Struct is formatted as a map object. 716 printMapFieldValue(field, message.getField(field)); 717 } 718 719 /** Prints google.protobuf.Value */ printValue(MessageOrBuilder message)720 private void printValue(MessageOrBuilder message) throws IOException { 721 // For a Value message, only the value of the field is formatted. 722 Map<FieldDescriptor, Object> fields = message.getAllFields(); 723 if (fields.isEmpty()) { 724 // No value set. 725 generator.print("null"); 726 return; 727 } 728 // A Value message can only have at most one field set (it only contains 729 // an oneof). 730 if (fields.size() != 1) { 731 throw new InvalidProtocolBufferException("Invalid Value type."); 732 } 733 for (Map.Entry<FieldDescriptor, Object> entry : fields.entrySet()) { 734 printSingleFieldValue(entry.getKey(), entry.getValue()); 735 } 736 } 737 738 /** Prints google.protobuf.ListValue */ printListValue(MessageOrBuilder message)739 private void printListValue(MessageOrBuilder message) throws IOException { 740 Descriptor descriptor = message.getDescriptorForType(); 741 FieldDescriptor field = descriptor.findFieldByName("values"); 742 if (field == null) { 743 throw new InvalidProtocolBufferException("Invalid ListValue type."); 744 } 745 printRepeatedFieldValue(field, message.getField(field)); 746 } 747 748 /** Prints a regular message with an optional type URL. */ print(MessageOrBuilder message, String typeUrl)749 private void print(MessageOrBuilder message, String typeUrl) throws IOException { 750 generator.print("{" + blankOrNewLine); 751 generator.indent(); 752 753 boolean printedField = false; 754 if (typeUrl != null) { 755 generator.print("\"@type\":" + blankOrSpace + gson.toJson(typeUrl)); 756 printedField = true; 757 } 758 Map<FieldDescriptor, Object> fieldsToPrint = null; 759 if (includingDefaultValueFields) { 760 fieldsToPrint = new TreeMap<FieldDescriptor, Object>(); 761 for (FieldDescriptor field : message.getDescriptorForType().getFields()) { 762 if (field.isOptional() 763 && field.getJavaType() == FieldDescriptor.JavaType.MESSAGE 764 && !message.hasField(field)) { 765 // Always skip empty optional message fields. If not we will recurse indefinitely if 766 // a message has itself as a sub-field. 767 continue; 768 } 769 fieldsToPrint.put(field, message.getField(field)); 770 } 771 } else { 772 fieldsToPrint = message.getAllFields(); 773 } 774 for (Map.Entry<FieldDescriptor, Object> field : fieldsToPrint.entrySet()) { 775 if (printedField) { 776 // Add line-endings for the previous field. 777 generator.print("," + blankOrNewLine); 778 } else { 779 printedField = true; 780 } 781 printField(field.getKey(), field.getValue()); 782 } 783 784 // Add line-endings for the last field. 785 if (printedField) { 786 generator.print(blankOrNewLine); 787 } 788 generator.outdent(); 789 generator.print("}"); 790 } 791 printField(FieldDescriptor field, Object value)792 private void printField(FieldDescriptor field, Object value) throws IOException { 793 if (preservingProtoFieldNames) { 794 generator.print("\"" + field.getName() + "\":" + blankOrSpace); 795 } else { 796 generator.print("\"" + field.getJsonName() + "\":" + blankOrSpace); 797 } 798 if (field.isMapField()) { 799 printMapFieldValue(field, value); 800 } else if (field.isRepeated()) { 801 printRepeatedFieldValue(field, value); 802 } else { 803 printSingleFieldValue(field, value); 804 } 805 } 806 807 @SuppressWarnings("rawtypes") printRepeatedFieldValue(FieldDescriptor field, Object value)808 private void printRepeatedFieldValue(FieldDescriptor field, Object value) throws IOException { 809 generator.print("["); 810 boolean printedElement = false; 811 for (Object element : (List) value) { 812 if (printedElement) { 813 generator.print("," + blankOrSpace); 814 } else { 815 printedElement = true; 816 } 817 printSingleFieldValue(field, element); 818 } 819 generator.print("]"); 820 } 821 822 @SuppressWarnings("rawtypes") printMapFieldValue(FieldDescriptor field, Object value)823 private void printMapFieldValue(FieldDescriptor field, Object value) throws IOException { 824 Descriptor type = field.getMessageType(); 825 FieldDescriptor keyField = type.findFieldByName("key"); 826 FieldDescriptor valueField = type.findFieldByName("value"); 827 if (keyField == null || valueField == null) { 828 throw new InvalidProtocolBufferException("Invalid map field."); 829 } 830 generator.print("{" + blankOrNewLine); 831 generator.indent(); 832 boolean printedElement = false; 833 for (Object element : (List) value) { 834 Message entry = (Message) element; 835 Object entryKey = entry.getField(keyField); 836 Object entryValue = entry.getField(valueField); 837 if (printedElement) { 838 generator.print("," + blankOrNewLine); 839 } else { 840 printedElement = true; 841 } 842 // Key fields are always double-quoted. 843 printSingleFieldValue(keyField, entryKey, true); 844 generator.print(":" + blankOrSpace); 845 printSingleFieldValue(valueField, entryValue); 846 } 847 if (printedElement) { 848 generator.print(blankOrNewLine); 849 } 850 generator.outdent(); 851 generator.print("}"); 852 } 853 printSingleFieldValue(FieldDescriptor field, Object value)854 private void printSingleFieldValue(FieldDescriptor field, Object value) throws IOException { 855 printSingleFieldValue(field, value, false); 856 } 857 858 /** 859 * Prints a field's value in JSON format. 860 * 861 * @param alwaysWithQuotes whether to always add double-quotes to primitive 862 * types. 863 */ printSingleFieldValue( final FieldDescriptor field, final Object value, boolean alwaysWithQuotes)864 private void printSingleFieldValue( 865 final FieldDescriptor field, final Object value, boolean alwaysWithQuotes) 866 throws IOException { 867 switch (field.getType()) { 868 case INT32: 869 case SINT32: 870 case SFIXED32: 871 if (alwaysWithQuotes) { 872 generator.print("\""); 873 } 874 generator.print(((Integer) value).toString()); 875 if (alwaysWithQuotes) { 876 generator.print("\""); 877 } 878 break; 879 880 case INT64: 881 case SINT64: 882 case SFIXED64: 883 generator.print("\"" + ((Long) value).toString() + "\""); 884 break; 885 886 case BOOL: 887 if (alwaysWithQuotes) { 888 generator.print("\""); 889 } 890 if (((Boolean) value).booleanValue()) { 891 generator.print("true"); 892 } else { 893 generator.print("false"); 894 } 895 if (alwaysWithQuotes) { 896 generator.print("\""); 897 } 898 break; 899 900 case FLOAT: 901 Float floatValue = (Float) value; 902 if (floatValue.isNaN()) { 903 generator.print("\"NaN\""); 904 } else if (floatValue.isInfinite()) { 905 if (floatValue < 0) { 906 generator.print("\"-Infinity\""); 907 } else { 908 generator.print("\"Infinity\""); 909 } 910 } else { 911 if (alwaysWithQuotes) { 912 generator.print("\""); 913 } 914 generator.print(floatValue.toString()); 915 if (alwaysWithQuotes) { 916 generator.print("\""); 917 } 918 } 919 break; 920 921 case DOUBLE: 922 Double doubleValue = (Double) value; 923 if (doubleValue.isNaN()) { 924 generator.print("\"NaN\""); 925 } else if (doubleValue.isInfinite()) { 926 if (doubleValue < 0) { 927 generator.print("\"-Infinity\""); 928 } else { 929 generator.print("\"Infinity\""); 930 } 931 } else { 932 if (alwaysWithQuotes) { 933 generator.print("\""); 934 } 935 generator.print(doubleValue.toString()); 936 if (alwaysWithQuotes) { 937 generator.print("\""); 938 } 939 } 940 break; 941 942 case UINT32: 943 case FIXED32: 944 if (alwaysWithQuotes) { 945 generator.print("\""); 946 } 947 generator.print(unsignedToString((Integer) value)); 948 if (alwaysWithQuotes) { 949 generator.print("\""); 950 } 951 break; 952 953 case UINT64: 954 case FIXED64: 955 generator.print("\"" + unsignedToString((Long) value) + "\""); 956 break; 957 958 case STRING: 959 generator.print(gson.toJson(value)); 960 break; 961 962 case BYTES: 963 generator.print("\""); 964 generator.print(BaseEncoding.base64().encode(((ByteString) value).toByteArray())); 965 generator.print("\""); 966 break; 967 968 case ENUM: 969 // Special-case google.protobuf.NullValue (it's an Enum). 970 if (field.getEnumType().getFullName().equals("google.protobuf.NullValue")) { 971 // No matter what value it contains, we always print it as "null". 972 if (alwaysWithQuotes) { 973 generator.print("\""); 974 } 975 generator.print("null"); 976 if (alwaysWithQuotes) { 977 generator.print("\""); 978 } 979 } else { 980 if (((EnumValueDescriptor) value).getIndex() == -1) { 981 generator.print(String.valueOf(((EnumValueDescriptor) value).getNumber())); 982 } else { 983 generator.print("\"" + ((EnumValueDescriptor) value).getName() + "\""); 984 } 985 } 986 break; 987 988 case MESSAGE: 989 case GROUP: 990 print((Message) value); 991 break; 992 } 993 } 994 } 995 996 /** Convert an unsigned 32-bit integer to a string. */ unsignedToString(final int value)997 private static String unsignedToString(final int value) { 998 if (value >= 0) { 999 return Integer.toString(value); 1000 } else { 1001 return Long.toString(value & 0x00000000FFFFFFFFL); 1002 } 1003 } 1004 1005 /** Convert an unsigned 64-bit integer to a string. */ unsignedToString(final long value)1006 private static String unsignedToString(final long value) { 1007 if (value >= 0) { 1008 return Long.toString(value); 1009 } else { 1010 // Pull off the most-significant bit so that BigInteger doesn't think 1011 // the number is negative, then set it again using setBit(). 1012 return BigInteger.valueOf(value & Long.MAX_VALUE).setBit(Long.SIZE - 1).toString(); 1013 } 1014 } 1015 getTypeName(String typeUrl)1016 private static String getTypeName(String typeUrl) throws InvalidProtocolBufferException { 1017 String[] parts = typeUrl.split("/"); 1018 if (parts.length == 1) { 1019 throw new InvalidProtocolBufferException("Invalid type url found: " + typeUrl); 1020 } 1021 return parts[parts.length - 1]; 1022 } 1023 1024 private static class ParserImpl { 1025 private final TypeRegistry registry; 1026 private final JsonParser jsonParser; 1027 ParserImpl(TypeRegistry registry)1028 ParserImpl(TypeRegistry registry) { 1029 this.registry = registry; 1030 this.jsonParser = new JsonParser(); 1031 } 1032 merge(Reader json, Message.Builder builder)1033 void merge(Reader json, Message.Builder builder) throws IOException { 1034 JsonReader reader = new JsonReader(json); 1035 reader.setLenient(false); 1036 merge(jsonParser.parse(reader), builder); 1037 } 1038 merge(String json, Message.Builder builder)1039 void merge(String json, Message.Builder builder) throws InvalidProtocolBufferException { 1040 try { 1041 JsonReader reader = new JsonReader(new StringReader(json)); 1042 reader.setLenient(false); 1043 merge(jsonParser.parse(reader), builder); 1044 } catch (InvalidProtocolBufferException e) { 1045 throw e; 1046 } catch (Exception e) { 1047 // We convert all exceptions from JSON parsing to our own exceptions. 1048 throw new InvalidProtocolBufferException(e.getMessage()); 1049 } 1050 } 1051 1052 private interface WellKnownTypeParser { merge(ParserImpl parser, JsonElement json, Message.Builder builder)1053 void merge(ParserImpl parser, JsonElement json, Message.Builder builder) 1054 throws InvalidProtocolBufferException; 1055 } 1056 1057 private static final Map<String, WellKnownTypeParser> wellKnownTypeParsers = 1058 buildWellKnownTypeParsers(); 1059 buildWellKnownTypeParsers()1060 private static Map<String, WellKnownTypeParser> buildWellKnownTypeParsers() { 1061 Map<String, WellKnownTypeParser> parsers = new HashMap<String, WellKnownTypeParser>(); 1062 // Special-case Any. 1063 parsers.put( 1064 Any.getDescriptor().getFullName(), 1065 new WellKnownTypeParser() { 1066 @Override 1067 public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) 1068 throws InvalidProtocolBufferException { 1069 parser.mergeAny(json, builder); 1070 } 1071 }); 1072 // Special-case wrapper types. 1073 WellKnownTypeParser wrappersPrinter = 1074 new WellKnownTypeParser() { 1075 @Override 1076 public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) 1077 throws InvalidProtocolBufferException { 1078 parser.mergeWrapper(json, builder); 1079 } 1080 }; 1081 parsers.put(BoolValue.getDescriptor().getFullName(), wrappersPrinter); 1082 parsers.put(Int32Value.getDescriptor().getFullName(), wrappersPrinter); 1083 parsers.put(UInt32Value.getDescriptor().getFullName(), wrappersPrinter); 1084 parsers.put(Int64Value.getDescriptor().getFullName(), wrappersPrinter); 1085 parsers.put(UInt64Value.getDescriptor().getFullName(), wrappersPrinter); 1086 parsers.put(StringValue.getDescriptor().getFullName(), wrappersPrinter); 1087 parsers.put(BytesValue.getDescriptor().getFullName(), wrappersPrinter); 1088 parsers.put(FloatValue.getDescriptor().getFullName(), wrappersPrinter); 1089 parsers.put(DoubleValue.getDescriptor().getFullName(), wrappersPrinter); 1090 // Special-case Timestamp. 1091 parsers.put( 1092 Timestamp.getDescriptor().getFullName(), 1093 new WellKnownTypeParser() { 1094 @Override 1095 public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) 1096 throws InvalidProtocolBufferException { 1097 parser.mergeTimestamp(json, builder); 1098 } 1099 }); 1100 // Special-case Duration. 1101 parsers.put( 1102 Duration.getDescriptor().getFullName(), 1103 new WellKnownTypeParser() { 1104 @Override 1105 public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) 1106 throws InvalidProtocolBufferException { 1107 parser.mergeDuration(json, builder); 1108 } 1109 }); 1110 // Special-case FieldMask. 1111 parsers.put( 1112 FieldMask.getDescriptor().getFullName(), 1113 new WellKnownTypeParser() { 1114 @Override 1115 public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) 1116 throws InvalidProtocolBufferException { 1117 parser.mergeFieldMask(json, builder); 1118 } 1119 }); 1120 // Special-case Struct. 1121 parsers.put( 1122 Struct.getDescriptor().getFullName(), 1123 new WellKnownTypeParser() { 1124 @Override 1125 public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) 1126 throws InvalidProtocolBufferException { 1127 parser.mergeStruct(json, builder); 1128 } 1129 }); 1130 // Special-case ListValue. 1131 parsers.put( 1132 ListValue.getDescriptor().getFullName(), 1133 new WellKnownTypeParser() { 1134 @Override 1135 public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) 1136 throws InvalidProtocolBufferException { 1137 parser.mergeListValue(json, builder); 1138 } 1139 }); 1140 // Special-case Value. 1141 parsers.put( 1142 Value.getDescriptor().getFullName(), 1143 new WellKnownTypeParser() { 1144 @Override 1145 public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) 1146 throws InvalidProtocolBufferException { 1147 parser.mergeValue(json, builder); 1148 } 1149 }); 1150 return parsers; 1151 } 1152 merge(JsonElement json, Message.Builder builder)1153 private void merge(JsonElement json, Message.Builder builder) 1154 throws InvalidProtocolBufferException { 1155 WellKnownTypeParser specialParser = 1156 wellKnownTypeParsers.get(builder.getDescriptorForType().getFullName()); 1157 if (specialParser != null) { 1158 specialParser.merge(this, json, builder); 1159 return; 1160 } 1161 mergeMessage(json, builder, false); 1162 } 1163 1164 // Maps from camel-case field names to FieldDescriptor. 1165 private final Map<Descriptor, Map<String, FieldDescriptor>> fieldNameMaps = 1166 new HashMap<Descriptor, Map<String, FieldDescriptor>>(); 1167 getFieldNameMap(Descriptor descriptor)1168 private Map<String, FieldDescriptor> getFieldNameMap(Descriptor descriptor) { 1169 if (!fieldNameMaps.containsKey(descriptor)) { 1170 Map<String, FieldDescriptor> fieldNameMap = new HashMap<String, FieldDescriptor>(); 1171 for (FieldDescriptor field : descriptor.getFields()) { 1172 fieldNameMap.put(field.getName(), field); 1173 fieldNameMap.put(field.getJsonName(), field); 1174 } 1175 fieldNameMaps.put(descriptor, fieldNameMap); 1176 return fieldNameMap; 1177 } 1178 return fieldNameMaps.get(descriptor); 1179 } 1180 mergeMessage(JsonElement json, Message.Builder builder, boolean skipTypeUrl)1181 private void mergeMessage(JsonElement json, Message.Builder builder, boolean skipTypeUrl) 1182 throws InvalidProtocolBufferException { 1183 if (!(json instanceof JsonObject)) { 1184 throw new InvalidProtocolBufferException("Expect message object but got: " + json); 1185 } 1186 JsonObject object = (JsonObject) json; 1187 Map<String, FieldDescriptor> fieldNameMap = getFieldNameMap(builder.getDescriptorForType()); 1188 for (Map.Entry<String, JsonElement> entry : object.entrySet()) { 1189 if (skipTypeUrl && entry.getKey().equals("@type")) { 1190 continue; 1191 } 1192 FieldDescriptor field = fieldNameMap.get(entry.getKey()); 1193 if (field == null) { 1194 throw new InvalidProtocolBufferException( 1195 "Cannot find field: " 1196 + entry.getKey() 1197 + " in message " 1198 + builder.getDescriptorForType().getFullName()); 1199 } 1200 mergeField(field, entry.getValue(), builder); 1201 } 1202 } 1203 mergeAny(JsonElement json, Message.Builder builder)1204 private void mergeAny(JsonElement json, Message.Builder builder) 1205 throws InvalidProtocolBufferException { 1206 Descriptor descriptor = builder.getDescriptorForType(); 1207 FieldDescriptor typeUrlField = descriptor.findFieldByName("type_url"); 1208 FieldDescriptor valueField = descriptor.findFieldByName("value"); 1209 // Validates type of the message. Note that we can't just cast the message 1210 // to com.google.protobuf.Any because it might be a DynamicMessage. 1211 if (typeUrlField == null 1212 || valueField == null 1213 || typeUrlField.getType() != FieldDescriptor.Type.STRING 1214 || valueField.getType() != FieldDescriptor.Type.BYTES) { 1215 throw new InvalidProtocolBufferException("Invalid Any type."); 1216 } 1217 1218 if (!(json instanceof JsonObject)) { 1219 throw new InvalidProtocolBufferException("Expect message object but got: " + json); 1220 } 1221 JsonObject object = (JsonObject) json; 1222 JsonElement typeUrlElement = object.get("@type"); 1223 if (typeUrlElement == null) { 1224 throw new InvalidProtocolBufferException("Missing type url when parsing: " + json); 1225 } 1226 String typeUrl = typeUrlElement.getAsString(); 1227 Descriptor contentType = registry.find(getTypeName(typeUrl)); 1228 if (contentType == null) { 1229 throw new InvalidProtocolBufferException("Cannot resolve type: " + typeUrl); 1230 } 1231 builder.setField(typeUrlField, typeUrl); 1232 Message.Builder contentBuilder = 1233 DynamicMessage.getDefaultInstance(contentType).newBuilderForType(); 1234 WellKnownTypeParser specialParser = wellKnownTypeParsers.get(contentType.getFullName()); 1235 if (specialParser != null) { 1236 JsonElement value = object.get("value"); 1237 if (value != null) { 1238 specialParser.merge(this, value, contentBuilder); 1239 } 1240 } else { 1241 mergeMessage(json, contentBuilder, true); 1242 } 1243 builder.setField(valueField, contentBuilder.build().toByteString()); 1244 } 1245 mergeFieldMask(JsonElement json, Message.Builder builder)1246 private void mergeFieldMask(JsonElement json, Message.Builder builder) 1247 throws InvalidProtocolBufferException { 1248 FieldMask value = FieldMaskUtil.fromJsonString(json.getAsString()); 1249 builder.mergeFrom(value.toByteString()); 1250 } 1251 mergeTimestamp(JsonElement json, Message.Builder builder)1252 private void mergeTimestamp(JsonElement json, Message.Builder builder) 1253 throws InvalidProtocolBufferException { 1254 try { 1255 Timestamp value = Timestamps.parse(json.getAsString()); 1256 builder.mergeFrom(value.toByteString()); 1257 } catch (ParseException e) { 1258 throw new InvalidProtocolBufferException("Failed to parse timestamp: " + json); 1259 } 1260 } 1261 mergeDuration(JsonElement json, Message.Builder builder)1262 private void mergeDuration(JsonElement json, Message.Builder builder) 1263 throws InvalidProtocolBufferException { 1264 try { 1265 Duration value = Durations.parse(json.getAsString()); 1266 builder.mergeFrom(value.toByteString()); 1267 } catch (ParseException e) { 1268 throw new InvalidProtocolBufferException("Failed to parse duration: " + json); 1269 } 1270 } 1271 mergeStruct(JsonElement json, Message.Builder builder)1272 private void mergeStruct(JsonElement json, Message.Builder builder) 1273 throws InvalidProtocolBufferException { 1274 Descriptor descriptor = builder.getDescriptorForType(); 1275 FieldDescriptor field = descriptor.findFieldByName("fields"); 1276 if (field == null) { 1277 throw new InvalidProtocolBufferException("Invalid Struct type."); 1278 } 1279 mergeMapField(field, json, builder); 1280 } 1281 mergeListValue(JsonElement json, Message.Builder builder)1282 private void mergeListValue(JsonElement json, Message.Builder builder) 1283 throws InvalidProtocolBufferException { 1284 Descriptor descriptor = builder.getDescriptorForType(); 1285 FieldDescriptor field = descriptor.findFieldByName("values"); 1286 if (field == null) { 1287 throw new InvalidProtocolBufferException("Invalid ListValue type."); 1288 } 1289 mergeRepeatedField(field, json, builder); 1290 } 1291 mergeValue(JsonElement json, Message.Builder builder)1292 private void mergeValue(JsonElement json, Message.Builder builder) 1293 throws InvalidProtocolBufferException { 1294 Descriptor type = builder.getDescriptorForType(); 1295 if (json instanceof JsonPrimitive) { 1296 JsonPrimitive primitive = (JsonPrimitive) json; 1297 if (primitive.isBoolean()) { 1298 builder.setField(type.findFieldByName("bool_value"), primitive.getAsBoolean()); 1299 } else if (primitive.isNumber()) { 1300 builder.setField(type.findFieldByName("number_value"), primitive.getAsDouble()); 1301 } else { 1302 builder.setField(type.findFieldByName("string_value"), primitive.getAsString()); 1303 } 1304 } else if (json instanceof JsonObject) { 1305 FieldDescriptor field = type.findFieldByName("struct_value"); 1306 Message.Builder structBuilder = builder.newBuilderForField(field); 1307 merge(json, structBuilder); 1308 builder.setField(field, structBuilder.build()); 1309 } else if (json instanceof JsonArray) { 1310 FieldDescriptor field = type.findFieldByName("list_value"); 1311 Message.Builder listBuilder = builder.newBuilderForField(field); 1312 merge(json, listBuilder); 1313 builder.setField(field, listBuilder.build()); 1314 } else { 1315 throw new IllegalStateException("Unexpected json data: " + json); 1316 } 1317 } 1318 mergeWrapper(JsonElement json, Message.Builder builder)1319 private void mergeWrapper(JsonElement json, Message.Builder builder) 1320 throws InvalidProtocolBufferException { 1321 Descriptor type = builder.getDescriptorForType(); 1322 FieldDescriptor field = type.findFieldByName("value"); 1323 if (field == null) { 1324 throw new InvalidProtocolBufferException("Invalid wrapper type: " + type.getFullName()); 1325 } 1326 builder.setField(field, parseFieldValue(field, json, builder)); 1327 } 1328 mergeField(FieldDescriptor field, JsonElement json, Message.Builder builder)1329 private void mergeField(FieldDescriptor field, JsonElement json, Message.Builder builder) 1330 throws InvalidProtocolBufferException { 1331 if (field.isRepeated()) { 1332 if (builder.getRepeatedFieldCount(field) > 0) { 1333 throw new InvalidProtocolBufferException( 1334 "Field " + field.getFullName() + " has already been set."); 1335 } 1336 } else { 1337 if (builder.hasField(field)) { 1338 throw new InvalidProtocolBufferException( 1339 "Field " + field.getFullName() + " has already been set."); 1340 } 1341 if (field.getContainingOneof() != null 1342 && builder.getOneofFieldDescriptor(field.getContainingOneof()) != null) { 1343 FieldDescriptor other = builder.getOneofFieldDescriptor(field.getContainingOneof()); 1344 throw new InvalidProtocolBufferException( 1345 "Cannot set field " 1346 + field.getFullName() 1347 + " because another field " 1348 + other.getFullName() 1349 + " belonging to the same oneof has already been set "); 1350 } 1351 } 1352 if (field.isRepeated() && json instanceof JsonNull) { 1353 // We allow "null" as value for all field types and treat it as if the 1354 // field is not present. 1355 return; 1356 } 1357 if (field.isMapField()) { 1358 mergeMapField(field, json, builder); 1359 } else if (field.isRepeated()) { 1360 mergeRepeatedField(field, json, builder); 1361 } else { 1362 Object value = parseFieldValue(field, json, builder); 1363 if (value != null) { 1364 builder.setField(field, value); 1365 } 1366 } 1367 } 1368 mergeMapField(FieldDescriptor field, JsonElement json, Message.Builder builder)1369 private void mergeMapField(FieldDescriptor field, JsonElement json, Message.Builder builder) 1370 throws InvalidProtocolBufferException { 1371 if (!(json instanceof JsonObject)) { 1372 throw new InvalidProtocolBufferException("Expect a map object but found: " + json); 1373 } 1374 Descriptor type = field.getMessageType(); 1375 FieldDescriptor keyField = type.findFieldByName("key"); 1376 FieldDescriptor valueField = type.findFieldByName("value"); 1377 if (keyField == null || valueField == null) { 1378 throw new InvalidProtocolBufferException("Invalid map field: " + field.getFullName()); 1379 } 1380 JsonObject object = (JsonObject) json; 1381 for (Map.Entry<String, JsonElement> entry : object.entrySet()) { 1382 Message.Builder entryBuilder = builder.newBuilderForField(field); 1383 Object key = parseFieldValue(keyField, new JsonPrimitive(entry.getKey()), entryBuilder); 1384 Object value = parseFieldValue(valueField, entry.getValue(), entryBuilder); 1385 if (value == null) { 1386 throw new InvalidProtocolBufferException("Map value cannot be null."); 1387 } 1388 entryBuilder.setField(keyField, key); 1389 entryBuilder.setField(valueField, value); 1390 builder.addRepeatedField(field, entryBuilder.build()); 1391 } 1392 } 1393 1394 /** 1395 * Gets the default value for a field type. Note that we use proto3 1396 * language defaults and ignore any default values set through the 1397 * proto "default" option. 1398 */ getDefaultValue(FieldDescriptor field, Message.Builder builder)1399 private Object getDefaultValue(FieldDescriptor field, Message.Builder builder) { 1400 switch (field.getType()) { 1401 case INT32: 1402 case SINT32: 1403 case SFIXED32: 1404 case UINT32: 1405 case FIXED32: 1406 return 0; 1407 case INT64: 1408 case SINT64: 1409 case SFIXED64: 1410 case UINT64: 1411 case FIXED64: 1412 return 0L; 1413 case FLOAT: 1414 return 0.0f; 1415 case DOUBLE: 1416 return 0.0; 1417 case BOOL: 1418 return false; 1419 case STRING: 1420 return ""; 1421 case BYTES: 1422 return ByteString.EMPTY; 1423 case ENUM: 1424 return field.getEnumType().getValues().get(0); 1425 case MESSAGE: 1426 case GROUP: 1427 return builder.newBuilderForField(field).getDefaultInstanceForType(); 1428 default: 1429 throw new IllegalStateException("Invalid field type: " + field.getType()); 1430 } 1431 } 1432 mergeRepeatedField( FieldDescriptor field, JsonElement json, Message.Builder builder)1433 private void mergeRepeatedField( 1434 FieldDescriptor field, JsonElement json, Message.Builder builder) 1435 throws InvalidProtocolBufferException { 1436 if (!(json instanceof JsonArray)) { 1437 throw new InvalidProtocolBufferException("Expect an array but found: " + json); 1438 } 1439 JsonArray array = (JsonArray) json; 1440 for (int i = 0; i < array.size(); ++i) { 1441 Object value = parseFieldValue(field, array.get(i), builder); 1442 if (value == null) { 1443 throw new InvalidProtocolBufferException("Repeated field elements cannot be null"); 1444 } 1445 builder.addRepeatedField(field, value); 1446 } 1447 } 1448 parseInt32(JsonElement json)1449 private int parseInt32(JsonElement json) throws InvalidProtocolBufferException { 1450 try { 1451 return Integer.parseInt(json.getAsString()); 1452 } catch (Exception e) { 1453 // Fall through. 1454 } 1455 // JSON doesn't distinguish between integer values and floating point values so "1" and 1456 // "1.000" are treated as equal in JSON. For this reason we accept floating point values for 1457 // integer fields as well as long as it actually is an integer (i.e., round(value) == value). 1458 try { 1459 BigDecimal value = new BigDecimal(json.getAsString()); 1460 return value.intValueExact(); 1461 } catch (Exception e) { 1462 throw new InvalidProtocolBufferException("Not an int32 value: " + json); 1463 } 1464 } 1465 parseInt64(JsonElement json)1466 private long parseInt64(JsonElement json) throws InvalidProtocolBufferException { 1467 try { 1468 return Long.parseLong(json.getAsString()); 1469 } catch (Exception e) { 1470 // Fall through. 1471 } 1472 // JSON doesn't distinguish between integer values and floating point values so "1" and 1473 // "1.000" are treated as equal in JSON. For this reason we accept floating point values for 1474 // integer fields as well as long as it actually is an integer (i.e., round(value) == value). 1475 try { 1476 BigDecimal value = new BigDecimal(json.getAsString()); 1477 return value.longValueExact(); 1478 } catch (Exception e) { 1479 throw new InvalidProtocolBufferException("Not an int32 value: " + json); 1480 } 1481 } 1482 parseUint32(JsonElement json)1483 private int parseUint32(JsonElement json) throws InvalidProtocolBufferException { 1484 try { 1485 long result = Long.parseLong(json.getAsString()); 1486 if (result < 0 || result > 0xFFFFFFFFL) { 1487 throw new InvalidProtocolBufferException("Out of range uint32 value: " + json); 1488 } 1489 return (int) result; 1490 } catch (InvalidProtocolBufferException e) { 1491 throw e; 1492 } catch (Exception e) { 1493 // Fall through. 1494 } 1495 // JSON doesn't distinguish between integer values and floating point values so "1" and 1496 // "1.000" are treated as equal in JSON. For this reason we accept floating point values for 1497 // integer fields as well as long as it actually is an integer (i.e., round(value) == value). 1498 try { 1499 BigDecimal decimalValue = new BigDecimal(json.getAsString()); 1500 BigInteger value = decimalValue.toBigIntegerExact(); 1501 if (value.signum() < 0 || value.compareTo(new BigInteger("FFFFFFFF", 16)) > 0) { 1502 throw new InvalidProtocolBufferException("Out of range uint32 value: " + json); 1503 } 1504 return value.intValue(); 1505 } catch (InvalidProtocolBufferException e) { 1506 throw e; 1507 } catch (Exception e) { 1508 throw new InvalidProtocolBufferException("Not an uint32 value: " + json); 1509 } 1510 } 1511 1512 private static final BigInteger MAX_UINT64 = new BigInteger("FFFFFFFFFFFFFFFF", 16); 1513 parseUint64(JsonElement json)1514 private long parseUint64(JsonElement json) throws InvalidProtocolBufferException { 1515 try { 1516 BigDecimal decimalValue = new BigDecimal(json.getAsString()); 1517 BigInteger value = decimalValue.toBigIntegerExact(); 1518 if (value.compareTo(BigInteger.ZERO) < 0 || value.compareTo(MAX_UINT64) > 0) { 1519 throw new InvalidProtocolBufferException("Out of range uint64 value: " + json); 1520 } 1521 return value.longValue(); 1522 } catch (InvalidProtocolBufferException e) { 1523 throw e; 1524 } catch (Exception e) { 1525 throw new InvalidProtocolBufferException("Not an uint64 value: " + json); 1526 } 1527 } 1528 parseBool(JsonElement json)1529 private boolean parseBool(JsonElement json) throws InvalidProtocolBufferException { 1530 if (json.getAsString().equals("true")) { 1531 return true; 1532 } 1533 if (json.getAsString().equals("false")) { 1534 return false; 1535 } 1536 throw new InvalidProtocolBufferException("Invalid bool value: " + json); 1537 } 1538 1539 private static final double EPSILON = 1e-6; 1540 parseFloat(JsonElement json)1541 private float parseFloat(JsonElement json) throws InvalidProtocolBufferException { 1542 if (json.getAsString().equals("NaN")) { 1543 return Float.NaN; 1544 } else if (json.getAsString().equals("Infinity")) { 1545 return Float.POSITIVE_INFINITY; 1546 } else if (json.getAsString().equals("-Infinity")) { 1547 return Float.NEGATIVE_INFINITY; 1548 } 1549 try { 1550 // We don't use Float.parseFloat() here because that function simply 1551 // accepts all double values. Here we parse the value into a Double 1552 // and do explicit range check on it. 1553 double value = Double.parseDouble(json.getAsString()); 1554 // When a float value is printed, the printed value might be a little 1555 // larger or smaller due to precision loss. Here we need to add a bit 1556 // of tolerance when checking whether the float value is in range. 1557 if (value > Float.MAX_VALUE * (1.0 + EPSILON) 1558 || value < -Float.MAX_VALUE * (1.0 + EPSILON)) { 1559 throw new InvalidProtocolBufferException("Out of range float value: " + json); 1560 } 1561 return (float) value; 1562 } catch (InvalidProtocolBufferException e) { 1563 throw e; 1564 } catch (Exception e) { 1565 throw new InvalidProtocolBufferException("Not a float value: " + json); 1566 } 1567 } 1568 1569 private static final BigDecimal MORE_THAN_ONE = new BigDecimal(String.valueOf(1.0 + EPSILON)); 1570 // When a float value is printed, the printed value might be a little 1571 // larger or smaller due to precision loss. Here we need to add a bit 1572 // of tolerance when checking whether the float value is in range. 1573 private static final BigDecimal MAX_DOUBLE = 1574 new BigDecimal(String.valueOf(Double.MAX_VALUE)).multiply(MORE_THAN_ONE); 1575 private static final BigDecimal MIN_DOUBLE = 1576 new BigDecimal(String.valueOf(-Double.MAX_VALUE)).multiply(MORE_THAN_ONE); 1577 parseDouble(JsonElement json)1578 private double parseDouble(JsonElement json) throws InvalidProtocolBufferException { 1579 if (json.getAsString().equals("NaN")) { 1580 return Double.NaN; 1581 } else if (json.getAsString().equals("Infinity")) { 1582 return Double.POSITIVE_INFINITY; 1583 } else if (json.getAsString().equals("-Infinity")) { 1584 return Double.NEGATIVE_INFINITY; 1585 } 1586 try { 1587 // We don't use Double.parseDouble() here because that function simply 1588 // accepts all values. Here we parse the value into a BigDecimal and do 1589 // explicit range check on it. 1590 BigDecimal value = new BigDecimal(json.getAsString()); 1591 if (value.compareTo(MAX_DOUBLE) > 0 || value.compareTo(MIN_DOUBLE) < 0) { 1592 throw new InvalidProtocolBufferException("Out of range double value: " + json); 1593 } 1594 return value.doubleValue(); 1595 } catch (InvalidProtocolBufferException e) { 1596 throw e; 1597 } catch (Exception e) { 1598 throw new InvalidProtocolBufferException("Not an double value: " + json); 1599 } 1600 } 1601 parseString(JsonElement json)1602 private String parseString(JsonElement json) { 1603 return json.getAsString(); 1604 } 1605 parseBytes(JsonElement json)1606 private ByteString parseBytes(JsonElement json) throws InvalidProtocolBufferException { 1607 String encoded = json.getAsString(); 1608 if (encoded.length() % 4 != 0) { 1609 throw new InvalidProtocolBufferException( 1610 "Bytes field is not encoded in standard BASE64 with paddings: " + encoded); 1611 } 1612 return ByteString.copyFrom(BaseEncoding.base64().decode(json.getAsString())); 1613 } 1614 parseEnum(EnumDescriptor enumDescriptor, JsonElement json)1615 private EnumValueDescriptor parseEnum(EnumDescriptor enumDescriptor, JsonElement json) 1616 throws InvalidProtocolBufferException { 1617 String value = json.getAsString(); 1618 EnumValueDescriptor result = enumDescriptor.findValueByName(value); 1619 if (result == null) { 1620 // Try to interpret the value as a number. 1621 try { 1622 int numericValue = parseInt32(json); 1623 if (enumDescriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3) { 1624 result = enumDescriptor.findValueByNumberCreatingIfUnknown(numericValue); 1625 } else { 1626 result = enumDescriptor.findValueByNumber(numericValue); 1627 } 1628 } catch (InvalidProtocolBufferException e) { 1629 // Fall through. This exception is about invalid int32 value we get from parseInt32() but 1630 // that's not the exception we want the user to see. Since result == null, we will throw 1631 // an exception later. 1632 } 1633 1634 if (result == null) { 1635 throw new InvalidProtocolBufferException( 1636 "Invalid enum value: " + value + " for enum type: " + enumDescriptor.getFullName()); 1637 } 1638 } 1639 return result; 1640 } 1641 parseFieldValue(FieldDescriptor field, JsonElement json, Message.Builder builder)1642 private Object parseFieldValue(FieldDescriptor field, JsonElement json, Message.Builder builder) 1643 throws InvalidProtocolBufferException { 1644 if (json instanceof JsonNull) { 1645 if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE 1646 && field.getMessageType().getFullName().equals(Value.getDescriptor().getFullName())) { 1647 // For every other type, "null" means absence, but for the special 1648 // Value message, it means the "null_value" field has been set. 1649 Value value = Value.newBuilder().setNullValueValue(0).build(); 1650 return builder.newBuilderForField(field).mergeFrom(value.toByteString()).build(); 1651 } else if (field.getJavaType() == FieldDescriptor.JavaType.ENUM 1652 && field.getEnumType().getFullName().equals(NullValue.getDescriptor().getFullName())) { 1653 // If the type of the field is a NullValue, then the value should be explicitly set. 1654 return field.getEnumType().findValueByNumber(0); 1655 } 1656 return null; 1657 } 1658 switch (field.getType()) { 1659 case INT32: 1660 case SINT32: 1661 case SFIXED32: 1662 return parseInt32(json); 1663 1664 case INT64: 1665 case SINT64: 1666 case SFIXED64: 1667 return parseInt64(json); 1668 1669 case BOOL: 1670 return parseBool(json); 1671 1672 case FLOAT: 1673 return parseFloat(json); 1674 1675 case DOUBLE: 1676 return parseDouble(json); 1677 1678 case UINT32: 1679 case FIXED32: 1680 return parseUint32(json); 1681 1682 case UINT64: 1683 case FIXED64: 1684 return parseUint64(json); 1685 1686 case STRING: 1687 return parseString(json); 1688 1689 case BYTES: 1690 return parseBytes(json); 1691 1692 case ENUM: 1693 return parseEnum(field.getEnumType(), json); 1694 1695 case MESSAGE: 1696 case GROUP: 1697 Message.Builder subBuilder = builder.newBuilderForField(field); 1698 merge(json, subBuilder); 1699 return subBuilder.build(); 1700 1701 default: 1702 throw new InvalidProtocolBufferException("Invalid field type: " + field.getType()); 1703 } 1704 } 1705 } 1706 } 1707