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"""Contains routines for printing protocol messages in JSON format. 32 33Simple usage example: 34 35 # Create a proto object and serialize it to a json format string. 36 message = my_proto_pb2.MyMessage(foo='bar') 37 json_string = json_format.MessageToJson(message) 38 39 # Parse a json format string to proto object. 40 message = json_format.Parse(json_string, my_proto_pb2.MyMessage()) 41""" 42 43__author__ = 'jieluo@google.com (Jie Luo)' 44 45# pylint: disable=g-statement-before-imports,g-import-not-at-top 46try: 47 from collections import OrderedDict 48except ImportError: 49 from ordereddict import OrderedDict # PY26 50# pylint: enable=g-statement-before-imports,g-import-not-at-top 51 52import base64 53import json 54import math 55 56from operator import methodcaller 57 58import re 59import sys 60 61import six 62 63from google.protobuf import descriptor 64from google.protobuf import symbol_database 65 66 67_TIMESTAMPFOMAT = '%Y-%m-%dT%H:%M:%S' 68_INT_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_INT32, 69 descriptor.FieldDescriptor.CPPTYPE_UINT32, 70 descriptor.FieldDescriptor.CPPTYPE_INT64, 71 descriptor.FieldDescriptor.CPPTYPE_UINT64]) 72_INT64_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_INT64, 73 descriptor.FieldDescriptor.CPPTYPE_UINT64]) 74_FLOAT_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_FLOAT, 75 descriptor.FieldDescriptor.CPPTYPE_DOUBLE]) 76_INFINITY = 'Infinity' 77_NEG_INFINITY = '-Infinity' 78_NAN = 'NaN' 79 80_UNPAIRED_SURROGATE_PATTERN = re.compile(six.u( 81 r'[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]' 82)) 83 84_VALID_EXTENSION_NAME = re.compile(r'\[[a-zA-Z0-9\._]*\]$') 85 86 87class Error(Exception): 88 """Top-level module error for json_format.""" 89 90 91class SerializeToJsonError(Error): 92 """Thrown if serialization to JSON fails.""" 93 94 95class ParseError(Error): 96 """Thrown in case of parsing error.""" 97 98 99def MessageToJson( 100 message, 101 including_default_value_fields=False, 102 preserving_proto_field_name=False, 103 indent=2, 104 sort_keys=False, 105 use_integers_for_enums=False, 106 descriptor_pool=None): 107 """Converts protobuf message to JSON format. 108 109 Args: 110 message: The protocol buffers message instance to serialize. 111 including_default_value_fields: If True, singular primitive fields, 112 repeated fields, and map fields will always be serialized. If 113 False, only serialize non-empty fields. Singular message fields 114 and oneof fields are not affected by this option. 115 preserving_proto_field_name: If True, use the original proto field 116 names as defined in the .proto file. If False, convert the field 117 names to lowerCamelCase. 118 indent: The JSON object will be pretty-printed with this indent level. 119 An indent level of 0 or negative will only insert newlines. 120 sort_keys: If True, then the output will be sorted by field names. 121 use_integers_for_enums: If true, print integers instead of enum names. 122 descriptor_pool: A Descriptor Pool for resolving types. If None use the 123 default. 124 125 Returns: 126 A string containing the JSON formatted protocol buffer message. 127 """ 128 printer = _Printer( 129 including_default_value_fields, 130 preserving_proto_field_name, 131 use_integers_for_enums, 132 descriptor_pool) 133 return printer.ToJsonString(message, indent, sort_keys) 134 135 136def MessageToDict( 137 message, 138 including_default_value_fields=False, 139 preserving_proto_field_name=False, 140 use_integers_for_enums=False, 141 descriptor_pool=None): 142 """Converts protobuf message to a dictionary. 143 144 When the dictionary is encoded to JSON, it conforms to proto3 JSON spec. 145 146 Args: 147 message: The protocol buffers message instance to serialize. 148 including_default_value_fields: If True, singular primitive fields, 149 repeated fields, and map fields will always be serialized. If 150 False, only serialize non-empty fields. Singular message fields 151 and oneof fields are not affected by this option. 152 preserving_proto_field_name: If True, use the original proto field 153 names as defined in the .proto file. If False, convert the field 154 names to lowerCamelCase. 155 use_integers_for_enums: If true, print integers instead of enum names. 156 descriptor_pool: A Descriptor Pool for resolving types. If None use the 157 default. 158 159 Returns: 160 A dict representation of the protocol buffer message. 161 """ 162 printer = _Printer( 163 including_default_value_fields, 164 preserving_proto_field_name, 165 use_integers_for_enums, 166 descriptor_pool) 167 # pylint: disable=protected-access 168 return printer._MessageToJsonObject(message) 169 170 171def _IsMapEntry(field): 172 return (field.type == descriptor.FieldDescriptor.TYPE_MESSAGE and 173 field.message_type.has_options and 174 field.message_type.GetOptions().map_entry) 175 176 177class _Printer(object): 178 """JSON format printer for protocol message.""" 179 180 def __init__( 181 self, 182 including_default_value_fields=False, 183 preserving_proto_field_name=False, 184 use_integers_for_enums=False, 185 descriptor_pool=None): 186 self.including_default_value_fields = including_default_value_fields 187 self.preserving_proto_field_name = preserving_proto_field_name 188 self.use_integers_for_enums = use_integers_for_enums 189 self.descriptor_pool = descriptor_pool 190 191 def ToJsonString(self, message, indent, sort_keys): 192 js = self._MessageToJsonObject(message) 193 return json.dumps(js, indent=indent, sort_keys=sort_keys) 194 195 def _MessageToJsonObject(self, message): 196 """Converts message to an object according to Proto3 JSON Specification.""" 197 message_descriptor = message.DESCRIPTOR 198 full_name = message_descriptor.full_name 199 if _IsWrapperMessage(message_descriptor): 200 return self._WrapperMessageToJsonObject(message) 201 if full_name in _WKTJSONMETHODS: 202 return methodcaller(_WKTJSONMETHODS[full_name][0], message)(self) 203 js = {} 204 return self._RegularMessageToJsonObject(message, js) 205 206 def _RegularMessageToJsonObject(self, message, js): 207 """Converts normal message according to Proto3 JSON Specification.""" 208 fields = message.ListFields() 209 210 try: 211 for field, value in fields: 212 if self.preserving_proto_field_name: 213 name = field.name 214 else: 215 name = field.json_name 216 if _IsMapEntry(field): 217 # Convert a map field. 218 v_field = field.message_type.fields_by_name['value'] 219 js_map = {} 220 for key in value: 221 if isinstance(key, bool): 222 if key: 223 recorded_key = 'true' 224 else: 225 recorded_key = 'false' 226 else: 227 recorded_key = key 228 js_map[recorded_key] = self._FieldToJsonObject( 229 v_field, value[key]) 230 js[name] = js_map 231 elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: 232 # Convert a repeated field. 233 js[name] = [self._FieldToJsonObject(field, k) 234 for k in value] 235 elif field.is_extension: 236 full_qualifier = field.full_name[:-len(field.name)] 237 name = '[%s%s]' % (full_qualifier, name) 238 js[name] = self._FieldToJsonObject(field, value) 239 else: 240 js[name] = self._FieldToJsonObject(field, value) 241 242 # Serialize default value if including_default_value_fields is True. 243 if self.including_default_value_fields: 244 message_descriptor = message.DESCRIPTOR 245 for field in message_descriptor.fields: 246 # Singular message fields and oneof fields will not be affected. 247 if ((field.label != descriptor.FieldDescriptor.LABEL_REPEATED and 248 field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE) or 249 field.containing_oneof): 250 continue 251 if self.preserving_proto_field_name: 252 name = field.name 253 else: 254 name = field.json_name 255 if name in js: 256 # Skip the field which has been serailized already. 257 continue 258 if _IsMapEntry(field): 259 js[name] = {} 260 elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: 261 js[name] = [] 262 else: 263 js[name] = self._FieldToJsonObject(field, field.default_value) 264 265 except ValueError as e: 266 raise SerializeToJsonError( 267 'Failed to serialize {0} field: {1}.'.format(field.name, e)) 268 269 return js 270 271 def _FieldToJsonObject(self, field, value): 272 """Converts field value according to Proto3 JSON Specification.""" 273 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: 274 return self._MessageToJsonObject(value) 275 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM: 276 if self.use_integers_for_enums: 277 return value 278 enum_value = field.enum_type.values_by_number.get(value, None) 279 if enum_value is not None: 280 return enum_value.name 281 else: 282 if field.file.syntax == 'proto3': 283 return value 284 raise SerializeToJsonError('Enum field contains an integer value ' 285 'which can not mapped to an enum value.') 286 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING: 287 if field.type == descriptor.FieldDescriptor.TYPE_BYTES: 288 # Use base64 Data encoding for bytes 289 return base64.b64encode(value).decode('utf-8') 290 else: 291 return value 292 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL: 293 return bool(value) 294 elif field.cpp_type in _INT64_TYPES: 295 return str(value) 296 elif field.cpp_type in _FLOAT_TYPES: 297 if math.isinf(value): 298 if value < 0.0: 299 return _NEG_INFINITY 300 else: 301 return _INFINITY 302 if math.isnan(value): 303 return _NAN 304 return value 305 306 def _AnyMessageToJsonObject(self, message): 307 """Converts Any message according to Proto3 JSON Specification.""" 308 if not message.ListFields(): 309 return {} 310 # Must print @type first, use OrderedDict instead of {} 311 js = OrderedDict() 312 type_url = message.type_url 313 js['@type'] = type_url 314 sub_message = _CreateMessageFromTypeUrl(type_url, self.descriptor_pool) 315 sub_message.ParseFromString(message.value) 316 message_descriptor = sub_message.DESCRIPTOR 317 full_name = message_descriptor.full_name 318 if _IsWrapperMessage(message_descriptor): 319 js['value'] = self._WrapperMessageToJsonObject(sub_message) 320 return js 321 if full_name in _WKTJSONMETHODS: 322 js['value'] = methodcaller(_WKTJSONMETHODS[full_name][0], 323 sub_message)(self) 324 return js 325 return self._RegularMessageToJsonObject(sub_message, js) 326 327 def _GenericMessageToJsonObject(self, message): 328 """Converts message according to Proto3 JSON Specification.""" 329 # Duration, Timestamp and FieldMask have ToJsonString method to do the 330 # convert. Users can also call the method directly. 331 return message.ToJsonString() 332 333 def _ValueMessageToJsonObject(self, message): 334 """Converts Value message according to Proto3 JSON Specification.""" 335 which = message.WhichOneof('kind') 336 # If the Value message is not set treat as null_value when serialize 337 # to JSON. The parse back result will be different from original message. 338 if which is None or which == 'null_value': 339 return None 340 if which == 'list_value': 341 return self._ListValueMessageToJsonObject(message.list_value) 342 if which == 'struct_value': 343 value = message.struct_value 344 else: 345 value = getattr(message, which) 346 oneof_descriptor = message.DESCRIPTOR.fields_by_name[which] 347 return self._FieldToJsonObject(oneof_descriptor, value) 348 349 def _ListValueMessageToJsonObject(self, message): 350 """Converts ListValue message according to Proto3 JSON Specification.""" 351 return [self._ValueMessageToJsonObject(value) 352 for value in message.values] 353 354 def _StructMessageToJsonObject(self, message): 355 """Converts Struct message according to Proto3 JSON Specification.""" 356 fields = message.fields 357 ret = {} 358 for key in fields: 359 ret[key] = self._ValueMessageToJsonObject(fields[key]) 360 return ret 361 362 def _WrapperMessageToJsonObject(self, message): 363 return self._FieldToJsonObject( 364 message.DESCRIPTOR.fields_by_name['value'], message.value) 365 366 367def _IsWrapperMessage(message_descriptor): 368 return message_descriptor.file.name == 'google/protobuf/wrappers.proto' 369 370 371def _DuplicateChecker(js): 372 result = {} 373 for name, value in js: 374 if name in result: 375 raise ParseError('Failed to load JSON: duplicate key {0}.'.format(name)) 376 result[name] = value 377 return result 378 379 380def _CreateMessageFromTypeUrl(type_url, descriptor_pool): 381 """Creates a message from a type URL.""" 382 db = symbol_database.Default() 383 pool = db.pool if descriptor_pool is None else descriptor_pool 384 type_name = type_url.split('/')[-1] 385 try: 386 message_descriptor = pool.FindMessageTypeByName(type_name) 387 except KeyError: 388 raise TypeError( 389 'Can not find message descriptor by type_url: {0}.'.format(type_url)) 390 message_class = db.GetPrototype(message_descriptor) 391 return message_class() 392 393 394def Parse(text, message, ignore_unknown_fields=False, descriptor_pool=None): 395 """Parses a JSON representation of a protocol message into a message. 396 397 Args: 398 text: Message JSON representation. 399 message: A protocol buffer message to merge into. 400 ignore_unknown_fields: If True, do not raise errors for unknown fields. 401 descriptor_pool: A Descriptor Pool for resolving types. If None use the 402 default. 403 404 Returns: 405 The same message passed as argument. 406 407 Raises:: 408 ParseError: On JSON parsing problems. 409 """ 410 if not isinstance(text, six.text_type): text = text.decode('utf-8') 411 try: 412 js = json.loads(text, object_pairs_hook=_DuplicateChecker) 413 except ValueError as e: 414 raise ParseError('Failed to load JSON: {0}.'.format(str(e))) 415 return ParseDict(js, message, ignore_unknown_fields, descriptor_pool) 416 417 418def ParseDict(js_dict, 419 message, 420 ignore_unknown_fields=False, 421 descriptor_pool=None): 422 """Parses a JSON dictionary representation into a message. 423 424 Args: 425 js_dict: Dict representation of a JSON message. 426 message: A protocol buffer message to merge into. 427 ignore_unknown_fields: If True, do not raise errors for unknown fields. 428 descriptor_pool: A Descriptor Pool for resolving types. If None use the 429 default. 430 431 Returns: 432 The same message passed as argument. 433 """ 434 parser = _Parser(ignore_unknown_fields, descriptor_pool) 435 parser.ConvertMessage(js_dict, message) 436 return message 437 438 439_INT_OR_FLOAT = six.integer_types + (float,) 440 441 442class _Parser(object): 443 """JSON format parser for protocol message.""" 444 445 def __init__(self, ignore_unknown_fields, descriptor_pool): 446 self.ignore_unknown_fields = ignore_unknown_fields 447 self.descriptor_pool = descriptor_pool 448 449 def ConvertMessage(self, value, message): 450 """Convert a JSON object into a message. 451 452 Args: 453 value: A JSON object. 454 message: A WKT or regular protocol message to record the data. 455 456 Raises: 457 ParseError: In case of convert problems. 458 """ 459 message_descriptor = message.DESCRIPTOR 460 full_name = message_descriptor.full_name 461 if _IsWrapperMessage(message_descriptor): 462 self._ConvertWrapperMessage(value, message) 463 elif full_name in _WKTJSONMETHODS: 464 methodcaller(_WKTJSONMETHODS[full_name][1], value, message)(self) 465 else: 466 self._ConvertFieldValuePair(value, message) 467 468 def _ConvertFieldValuePair(self, js, message): 469 """Convert field value pairs into regular message. 470 471 Args: 472 js: A JSON object to convert the field value pairs. 473 message: A regular protocol message to record the data. 474 475 Raises: 476 ParseError: In case of problems converting. 477 """ 478 names = [] 479 message_descriptor = message.DESCRIPTOR 480 fields_by_json_name = dict((f.json_name, f) 481 for f in message_descriptor.fields) 482 for name in js: 483 try: 484 field = fields_by_json_name.get(name, None) 485 if not field: 486 field = message_descriptor.fields_by_name.get(name, None) 487 if not field and _VALID_EXTENSION_NAME.match(name): 488 if not message_descriptor.is_extendable: 489 raise ParseError('Message type {0} does not have extensions'.format( 490 message_descriptor.full_name)) 491 identifier = name[1:-1] # strip [] brackets 492 # pylint: disable=protected-access 493 field = message.Extensions._FindExtensionByName(identifier) 494 # pylint: enable=protected-access 495 if not field: 496 # Try looking for extension by the message type name, dropping the 497 # field name following the final . separator in full_name. 498 identifier = '.'.join(identifier.split('.')[:-1]) 499 # pylint: disable=protected-access 500 field = message.Extensions._FindExtensionByName(identifier) 501 # pylint: enable=protected-access 502 if not field: 503 if self.ignore_unknown_fields: 504 continue 505 raise ParseError( 506 ('Message type "{0}" has no field named "{1}".\n' 507 ' Available Fields(except extensions): {2}').format( 508 message_descriptor.full_name, name, 509 [f.json_name for f in message_descriptor.fields])) 510 if name in names: 511 raise ParseError('Message type "{0}" should not have multiple ' 512 '"{1}" fields.'.format( 513 message.DESCRIPTOR.full_name, name)) 514 names.append(name) 515 # Check no other oneof field is parsed. 516 if field.containing_oneof is not None: 517 oneof_name = field.containing_oneof.name 518 if oneof_name in names: 519 raise ParseError('Message type "{0}" should not have multiple ' 520 '"{1}" oneof fields.'.format( 521 message.DESCRIPTOR.full_name, oneof_name)) 522 names.append(oneof_name) 523 524 value = js[name] 525 if value is None: 526 if (field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE 527 and field.message_type.full_name == 'google.protobuf.Value'): 528 sub_message = getattr(message, field.name) 529 sub_message.null_value = 0 530 else: 531 message.ClearField(field.name) 532 continue 533 534 # Parse field value. 535 if _IsMapEntry(field): 536 message.ClearField(field.name) 537 self._ConvertMapFieldValue(value, message, field) 538 elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: 539 message.ClearField(field.name) 540 if not isinstance(value, list): 541 raise ParseError('repeated field {0} must be in [] which is ' 542 '{1}.'.format(name, value)) 543 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: 544 # Repeated message field. 545 for item in value: 546 sub_message = getattr(message, field.name).add() 547 # None is a null_value in Value. 548 if (item is None and 549 sub_message.DESCRIPTOR.full_name != 'google.protobuf.Value'): 550 raise ParseError('null is not allowed to be used as an element' 551 ' in a repeated field.') 552 self.ConvertMessage(item, sub_message) 553 else: 554 # Repeated scalar field. 555 for item in value: 556 if item is None: 557 raise ParseError('null is not allowed to be used as an element' 558 ' in a repeated field.') 559 getattr(message, field.name).append( 560 _ConvertScalarFieldValue(item, field)) 561 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: 562 if field.is_extension: 563 sub_message = message.Extensions[field] 564 else: 565 sub_message = getattr(message, field.name) 566 sub_message.SetInParent() 567 self.ConvertMessage(value, sub_message) 568 else: 569 if field.is_extension: 570 message.Extensions[field] = _ConvertScalarFieldValue(value, field) 571 else: 572 setattr(message, field.name, _ConvertScalarFieldValue(value, field)) 573 except ParseError as e: 574 if field and field.containing_oneof is None: 575 raise ParseError('Failed to parse {0} field: {1}.'.format(name, e)) 576 else: 577 raise ParseError(str(e)) 578 except ValueError as e: 579 raise ParseError('Failed to parse {0} field: {1}.'.format(name, e)) 580 except TypeError as e: 581 raise ParseError('Failed to parse {0} field: {1}.'.format(name, e)) 582 583 def _ConvertAnyMessage(self, value, message): 584 """Convert a JSON representation into Any message.""" 585 if isinstance(value, dict) and not value: 586 return 587 try: 588 type_url = value['@type'] 589 except KeyError: 590 raise ParseError('@type is missing when parsing any message.') 591 592 sub_message = _CreateMessageFromTypeUrl(type_url, self.descriptor_pool) 593 message_descriptor = sub_message.DESCRIPTOR 594 full_name = message_descriptor.full_name 595 if _IsWrapperMessage(message_descriptor): 596 self._ConvertWrapperMessage(value['value'], sub_message) 597 elif full_name in _WKTJSONMETHODS: 598 methodcaller( 599 _WKTJSONMETHODS[full_name][1], value['value'], sub_message)(self) 600 else: 601 del value['@type'] 602 self._ConvertFieldValuePair(value, sub_message) 603 value['@type'] = type_url 604 # Sets Any message 605 message.value = sub_message.SerializeToString() 606 message.type_url = type_url 607 608 def _ConvertGenericMessage(self, value, message): 609 """Convert a JSON representation into message with FromJsonString.""" 610 # Duration, Timestamp, FieldMask have a FromJsonString method to do the 611 # conversion. Users can also call the method directly. 612 try: 613 message.FromJsonString(value) 614 except ValueError as e: 615 raise ParseError(e) 616 617 def _ConvertValueMessage(self, value, message): 618 """Convert a JSON representation into Value message.""" 619 if isinstance(value, dict): 620 self._ConvertStructMessage(value, message.struct_value) 621 elif isinstance(value, list): 622 self. _ConvertListValueMessage(value, message.list_value) 623 elif value is None: 624 message.null_value = 0 625 elif isinstance(value, bool): 626 message.bool_value = value 627 elif isinstance(value, six.string_types): 628 message.string_value = value 629 elif isinstance(value, _INT_OR_FLOAT): 630 message.number_value = value 631 else: 632 raise ParseError('Unexpected type for Value message.') 633 634 def _ConvertListValueMessage(self, value, message): 635 """Convert a JSON representation into ListValue message.""" 636 if not isinstance(value, list): 637 raise ParseError( 638 'ListValue must be in [] which is {0}.'.format(value)) 639 message.ClearField('values') 640 for item in value: 641 self._ConvertValueMessage(item, message.values.add()) 642 643 def _ConvertStructMessage(self, value, message): 644 """Convert a JSON representation into Struct message.""" 645 if not isinstance(value, dict): 646 raise ParseError( 647 'Struct must be in a dict which is {0}.'.format(value)) 648 # Clear will mark the struct as modified so it will be created even if 649 # there are no values. 650 message.Clear() 651 for key in value: 652 self._ConvertValueMessage(value[key], message.fields[key]) 653 return 654 655 def _ConvertWrapperMessage(self, value, message): 656 """Convert a JSON representation into Wrapper message.""" 657 field = message.DESCRIPTOR.fields_by_name['value'] 658 setattr(message, 'value', _ConvertScalarFieldValue(value, field)) 659 660 def _ConvertMapFieldValue(self, value, message, field): 661 """Convert map field value for a message map field. 662 663 Args: 664 value: A JSON object to convert the map field value. 665 message: A protocol message to record the converted data. 666 field: The descriptor of the map field to be converted. 667 668 Raises: 669 ParseError: In case of convert problems. 670 """ 671 if not isinstance(value, dict): 672 raise ParseError( 673 'Map field {0} must be in a dict which is {1}.'.format( 674 field.name, value)) 675 key_field = field.message_type.fields_by_name['key'] 676 value_field = field.message_type.fields_by_name['value'] 677 for key in value: 678 key_value = _ConvertScalarFieldValue(key, key_field, True) 679 if value_field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: 680 self.ConvertMessage(value[key], getattr( 681 message, field.name)[key_value]) 682 else: 683 getattr(message, field.name)[key_value] = _ConvertScalarFieldValue( 684 value[key], value_field) 685 686 687def _ConvertScalarFieldValue(value, field, require_str=False): 688 """Convert a single scalar field value. 689 690 Args: 691 value: A scalar value to convert the scalar field value. 692 field: The descriptor of the field to convert. 693 require_str: If True, the field value must be a str. 694 695 Returns: 696 The converted scalar field value 697 698 Raises: 699 ParseError: In case of convert problems. 700 """ 701 if field.cpp_type in _INT_TYPES: 702 return _ConvertInteger(value) 703 elif field.cpp_type in _FLOAT_TYPES: 704 return _ConvertFloat(value) 705 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL: 706 return _ConvertBool(value, require_str) 707 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING: 708 if field.type == descriptor.FieldDescriptor.TYPE_BYTES: 709 return base64.b64decode(value) 710 else: 711 # Checking for unpaired surrogates appears to be unreliable, 712 # depending on the specific Python version, so we check manually. 713 if _UNPAIRED_SURROGATE_PATTERN.search(value): 714 raise ParseError('Unpaired surrogate') 715 return value 716 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM: 717 # Convert an enum value. 718 enum_value = field.enum_type.values_by_name.get(value, None) 719 if enum_value is None: 720 try: 721 number = int(value) 722 enum_value = field.enum_type.values_by_number.get(number, None) 723 except ValueError: 724 raise ParseError('Invalid enum value {0} for enum type {1}.'.format( 725 value, field.enum_type.full_name)) 726 if enum_value is None: 727 if field.file.syntax == 'proto3': 728 # Proto3 accepts unknown enums. 729 return number 730 raise ParseError('Invalid enum value {0} for enum type {1}.'.format( 731 value, field.enum_type.full_name)) 732 return enum_value.number 733 734 735def _ConvertInteger(value): 736 """Convert an integer. 737 738 Args: 739 value: A scalar value to convert. 740 741 Returns: 742 The integer value. 743 744 Raises: 745 ParseError: If an integer couldn't be consumed. 746 """ 747 if isinstance(value, float) and not value.is_integer(): 748 raise ParseError('Couldn\'t parse integer: {0}.'.format(value)) 749 750 if isinstance(value, six.text_type) and value.find(' ') != -1: 751 raise ParseError('Couldn\'t parse integer: "{0}".'.format(value)) 752 753 return int(value) 754 755 756def _ConvertFloat(value): 757 """Convert an floating point number.""" 758 if value == 'nan': 759 raise ParseError('Couldn\'t parse float "nan", use "NaN" instead.') 760 try: 761 # Assume Python compatible syntax. 762 return float(value) 763 except ValueError: 764 # Check alternative spellings. 765 if value == _NEG_INFINITY: 766 return float('-inf') 767 elif value == _INFINITY: 768 return float('inf') 769 elif value == _NAN: 770 return float('nan') 771 else: 772 raise ParseError('Couldn\'t parse float: {0}.'.format(value)) 773 774 775def _ConvertBool(value, require_str): 776 """Convert a boolean value. 777 778 Args: 779 value: A scalar value to convert. 780 require_str: If True, value must be a str. 781 782 Returns: 783 The bool parsed. 784 785 Raises: 786 ParseError: If a boolean value couldn't be consumed. 787 """ 788 if require_str: 789 if value == 'true': 790 return True 791 elif value == 'false': 792 return False 793 else: 794 raise ParseError('Expected "true" or "false", not {0}.'.format(value)) 795 796 if not isinstance(value, bool): 797 raise ParseError('Expected true or false without quotes.') 798 return value 799 800_WKTJSONMETHODS = { 801 'google.protobuf.Any': ['_AnyMessageToJsonObject', 802 '_ConvertAnyMessage'], 803 'google.protobuf.Duration': ['_GenericMessageToJsonObject', 804 '_ConvertGenericMessage'], 805 'google.protobuf.FieldMask': ['_GenericMessageToJsonObject', 806 '_ConvertGenericMessage'], 807 'google.protobuf.ListValue': ['_ListValueMessageToJsonObject', 808 '_ConvertListValueMessage'], 809 'google.protobuf.Struct': ['_StructMessageToJsonObject', 810 '_ConvertStructMessage'], 811 'google.protobuf.Timestamp': ['_GenericMessageToJsonObject', 812 '_ConvertGenericMessage'], 813 'google.protobuf.Value': ['_ValueMessageToJsonObject', 814 '_ConvertValueMessage'] 815} 816