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 45try: 46 from collections import OrderedDict 47except ImportError: 48 from ordereddict import OrderedDict #PY26 49import base64 50import json 51import math 52import re 53import six 54import sys 55 56from operator import methodcaller 57from google.protobuf import descriptor 58from google.protobuf import symbol_database 59 60_TIMESTAMPFOMAT = '%Y-%m-%dT%H:%M:%S' 61_INT_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_INT32, 62 descriptor.FieldDescriptor.CPPTYPE_UINT32, 63 descriptor.FieldDescriptor.CPPTYPE_INT64, 64 descriptor.FieldDescriptor.CPPTYPE_UINT64]) 65_INT64_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_INT64, 66 descriptor.FieldDescriptor.CPPTYPE_UINT64]) 67_FLOAT_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_FLOAT, 68 descriptor.FieldDescriptor.CPPTYPE_DOUBLE]) 69_INFINITY = 'Infinity' 70_NEG_INFINITY = '-Infinity' 71_NAN = 'NaN' 72 73_UNPAIRED_SURROGATE_PATTERN = re.compile(six.u( 74 r'[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]' 75)) 76 77class Error(Exception): 78 """Top-level module error for json_format.""" 79 80 81class SerializeToJsonError(Error): 82 """Thrown if serialization to JSON fails.""" 83 84 85class ParseError(Error): 86 """Thrown in case of parsing error.""" 87 88 89def MessageToJson(message, including_default_value_fields=False): 90 """Converts protobuf message to JSON format. 91 92 Args: 93 message: The protocol buffers message instance to serialize. 94 including_default_value_fields: If True, singular primitive fields, 95 repeated fields, and map fields will always be serialized. If 96 False, only serialize non-empty fields. Singular message fields 97 and oneof fields are not affected by this option. 98 99 Returns: 100 A string containing the JSON formatted protocol buffer message. 101 """ 102 printer = _Printer(including_default_value_fields) 103 return printer.ToJsonString(message) 104 105 106def _IsMapEntry(field): 107 return (field.type == descriptor.FieldDescriptor.TYPE_MESSAGE and 108 field.message_type.has_options and 109 field.message_type.GetOptions().map_entry) 110 111 112class _Printer(object): 113 """JSON format printer for protocol message.""" 114 115 def __init__(self, 116 including_default_value_fields=False): 117 self.including_default_value_fields = including_default_value_fields 118 119 def ToJsonString(self, message): 120 js = self._MessageToJsonObject(message) 121 return json.dumps(js, indent=2) 122 123 def _MessageToJsonObject(self, message): 124 """Converts message to an object according to Proto3 JSON Specification.""" 125 message_descriptor = message.DESCRIPTOR 126 full_name = message_descriptor.full_name 127 if _IsWrapperMessage(message_descriptor): 128 return self._WrapperMessageToJsonObject(message) 129 if full_name in _WKTJSONMETHODS: 130 return methodcaller(_WKTJSONMETHODS[full_name][0], message)(self) 131 js = {} 132 return self._RegularMessageToJsonObject(message, js) 133 134 def _RegularMessageToJsonObject(self, message, js): 135 """Converts normal message according to Proto3 JSON Specification.""" 136 fields = message.ListFields() 137 138 try: 139 for field, value in fields: 140 name = field.camelcase_name 141 if _IsMapEntry(field): 142 # Convert a map field. 143 v_field = field.message_type.fields_by_name['value'] 144 js_map = {} 145 for key in value: 146 if isinstance(key, bool): 147 if key: 148 recorded_key = 'true' 149 else: 150 recorded_key = 'false' 151 else: 152 recorded_key = key 153 js_map[recorded_key] = self._FieldToJsonObject( 154 v_field, value[key]) 155 js[name] = js_map 156 elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: 157 # Convert a repeated field. 158 js[name] = [self._FieldToJsonObject(field, k) 159 for k in value] 160 else: 161 js[name] = self._FieldToJsonObject(field, value) 162 163 # Serialize default value if including_default_value_fields is True. 164 if self.including_default_value_fields: 165 message_descriptor = message.DESCRIPTOR 166 for field in message_descriptor.fields: 167 # Singular message fields and oneof fields will not be affected. 168 if ((field.label != descriptor.FieldDescriptor.LABEL_REPEATED and 169 field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE) or 170 field.containing_oneof): 171 continue 172 name = field.camelcase_name 173 if name in js: 174 # Skip the field which has been serailized already. 175 continue 176 if _IsMapEntry(field): 177 js[name] = {} 178 elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: 179 js[name] = [] 180 else: 181 js[name] = self._FieldToJsonObject(field, field.default_value) 182 183 except ValueError as e: 184 raise SerializeToJsonError( 185 'Failed to serialize {0} field: {1}.'.format(field.name, e)) 186 187 return js 188 189 def _FieldToJsonObject(self, field, value): 190 """Converts field value according to Proto3 JSON Specification.""" 191 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: 192 return self._MessageToJsonObject(value) 193 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM: 194 enum_value = field.enum_type.values_by_number.get(value, None) 195 if enum_value is not None: 196 return enum_value.name 197 else: 198 raise SerializeToJsonError('Enum field contains an integer value ' 199 'which can not mapped to an enum value.') 200 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING: 201 if field.type == descriptor.FieldDescriptor.TYPE_BYTES: 202 # Use base64 Data encoding for bytes 203 return base64.b64encode(value).decode('utf-8') 204 else: 205 return value 206 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL: 207 return bool(value) 208 elif field.cpp_type in _INT64_TYPES: 209 return str(value) 210 elif field.cpp_type in _FLOAT_TYPES: 211 if math.isinf(value): 212 if value < 0.0: 213 return _NEG_INFINITY 214 else: 215 return _INFINITY 216 if math.isnan(value): 217 return _NAN 218 return value 219 220 def _AnyMessageToJsonObject(self, message): 221 """Converts Any message according to Proto3 JSON Specification.""" 222 if not message.ListFields(): 223 return {} 224 # Must print @type first, use OrderedDict instead of {} 225 js = OrderedDict() 226 type_url = message.type_url 227 js['@type'] = type_url 228 sub_message = _CreateMessageFromTypeUrl(type_url) 229 sub_message.ParseFromString(message.value) 230 message_descriptor = sub_message.DESCRIPTOR 231 full_name = message_descriptor.full_name 232 if _IsWrapperMessage(message_descriptor): 233 js['value'] = self._WrapperMessageToJsonObject(sub_message) 234 return js 235 if full_name in _WKTJSONMETHODS: 236 js['value'] = methodcaller(_WKTJSONMETHODS[full_name][0], 237 sub_message)(self) 238 return js 239 return self._RegularMessageToJsonObject(sub_message, js) 240 241 def _GenericMessageToJsonObject(self, message): 242 """Converts message according to Proto3 JSON Specification.""" 243 # Duration, Timestamp and FieldMask have ToJsonString method to do the 244 # convert. Users can also call the method directly. 245 return message.ToJsonString() 246 247 def _ValueMessageToJsonObject(self, message): 248 """Converts Value message according to Proto3 JSON Specification.""" 249 which = message.WhichOneof('kind') 250 # If the Value message is not set treat as null_value when serialize 251 # to JSON. The parse back result will be different from original message. 252 if which is None or which == 'null_value': 253 return None 254 if which == 'list_value': 255 return self._ListValueMessageToJsonObject(message.list_value) 256 if which == 'struct_value': 257 value = message.struct_value 258 else: 259 value = getattr(message, which) 260 oneof_descriptor = message.DESCRIPTOR.fields_by_name[which] 261 return self._FieldToJsonObject(oneof_descriptor, value) 262 263 def _ListValueMessageToJsonObject(self, message): 264 """Converts ListValue message according to Proto3 JSON Specification.""" 265 return [self._ValueMessageToJsonObject(value) 266 for value in message.values] 267 268 def _StructMessageToJsonObject(self, message): 269 """Converts Struct message according to Proto3 JSON Specification.""" 270 fields = message.fields 271 ret = {} 272 for key in fields: 273 ret[key] = self._ValueMessageToJsonObject(fields[key]) 274 return ret 275 276 def _WrapperMessageToJsonObject(self, message): 277 return self._FieldToJsonObject( 278 message.DESCRIPTOR.fields_by_name['value'], message.value) 279 280 281def _IsWrapperMessage(message_descriptor): 282 return message_descriptor.file.name == 'google/protobuf/wrappers.proto' 283 284 285def _DuplicateChecker(js): 286 result = {} 287 for name, value in js: 288 if name in result: 289 raise ParseError('Failed to load JSON: duplicate key {0}.'.format(name)) 290 result[name] = value 291 return result 292 293 294def _CreateMessageFromTypeUrl(type_url): 295 # TODO(jieluo): Should add a way that users can register the type resolver 296 # instead of the default one. 297 db = symbol_database.Default() 298 type_name = type_url.split('/')[-1] 299 try: 300 message_descriptor = db.pool.FindMessageTypeByName(type_name) 301 except KeyError: 302 raise TypeError( 303 'Can not find message descriptor by type_url: {0}.'.format(type_url)) 304 message_class = db.GetPrototype(message_descriptor) 305 return message_class() 306 307 308def Parse(text, message, ignore_unknown_fields=False): 309 """Parses a JSON representation of a protocol message into a message. 310 311 Args: 312 text: Message JSON representation. 313 message: A protocol beffer message to merge into. 314 ignore_unknown_fields: If True, do not raise errors for unknown fields. 315 316 Returns: 317 The same message passed as argument. 318 319 Raises:: 320 ParseError: On JSON parsing problems. 321 """ 322 if not isinstance(text, six.text_type): text = text.decode('utf-8') 323 try: 324 if sys.version_info < (2, 7): 325 # object_pair_hook is not supported before python2.7 326 js = json.loads(text) 327 else: 328 js = json.loads(text, object_pairs_hook=_DuplicateChecker) 329 except ValueError as e: 330 raise ParseError('Failed to load JSON: {0}.'.format(str(e))) 331 parser = _Parser(ignore_unknown_fields) 332 parser.ConvertMessage(js, message) 333 return message 334 335 336_INT_OR_FLOAT = six.integer_types + (float,) 337 338 339class _Parser(object): 340 """JSON format parser for protocol message.""" 341 342 def __init__(self, 343 ignore_unknown_fields): 344 self.ignore_unknown_fields = ignore_unknown_fields 345 346 def ConvertMessage(self, value, message): 347 """Convert a JSON object into a message. 348 349 Args: 350 value: A JSON object. 351 message: A WKT or regular protocol message to record the data. 352 353 Raises: 354 ParseError: In case of convert problems. 355 """ 356 message_descriptor = message.DESCRIPTOR 357 full_name = message_descriptor.full_name 358 if _IsWrapperMessage(message_descriptor): 359 self._ConvertWrapperMessage(value, message) 360 elif full_name in _WKTJSONMETHODS: 361 methodcaller(_WKTJSONMETHODS[full_name][1], value, message)(self) 362 else: 363 self._ConvertFieldValuePair(value, message) 364 365 def _ConvertFieldValuePair(self, js, message): 366 """Convert field value pairs into regular message. 367 368 Args: 369 js: A JSON object to convert the field value pairs. 370 message: A regular protocol message to record the data. 371 372 Raises: 373 ParseError: In case of problems converting. 374 """ 375 names = [] 376 message_descriptor = message.DESCRIPTOR 377 for name in js: 378 try: 379 field = message_descriptor.fields_by_camelcase_name.get(name, None) 380 if not field: 381 if self.ignore_unknown_fields: 382 continue 383 raise ParseError( 384 'Message type "{0}" has no field named "{1}".'.format( 385 message_descriptor.full_name, name)) 386 if name in names: 387 raise ParseError('Message type "{0}" should not have multiple ' 388 '"{1}" fields.'.format( 389 message.DESCRIPTOR.full_name, name)) 390 names.append(name) 391 # Check no other oneof field is parsed. 392 if field.containing_oneof is not None: 393 oneof_name = field.containing_oneof.name 394 if oneof_name in names: 395 raise ParseError('Message type "{0}" should not have multiple ' 396 '"{1}" oneof fields.'.format( 397 message.DESCRIPTOR.full_name, oneof_name)) 398 names.append(oneof_name) 399 400 value = js[name] 401 if value is None: 402 message.ClearField(field.name) 403 continue 404 405 # Parse field value. 406 if _IsMapEntry(field): 407 message.ClearField(field.name) 408 self._ConvertMapFieldValue(value, message, field) 409 elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: 410 message.ClearField(field.name) 411 if not isinstance(value, list): 412 raise ParseError('repeated field {0} must be in [] which is ' 413 '{1}.'.format(name, value)) 414 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: 415 # Repeated message field. 416 for item in value: 417 sub_message = getattr(message, field.name).add() 418 # None is a null_value in Value. 419 if (item is None and 420 sub_message.DESCRIPTOR.full_name != 'google.protobuf.Value'): 421 raise ParseError('null is not allowed to be used as an element' 422 ' in a repeated field.') 423 self.ConvertMessage(item, sub_message) 424 else: 425 # Repeated scalar field. 426 for item in value: 427 if item is None: 428 raise ParseError('null is not allowed to be used as an element' 429 ' in a repeated field.') 430 getattr(message, field.name).append( 431 _ConvertScalarFieldValue(item, field)) 432 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: 433 sub_message = getattr(message, field.name) 434 self.ConvertMessage(value, sub_message) 435 else: 436 setattr(message, field.name, _ConvertScalarFieldValue(value, field)) 437 except ParseError as e: 438 if field and field.containing_oneof is None: 439 raise ParseError('Failed to parse {0} field: {1}'.format(name, e)) 440 else: 441 raise ParseError(str(e)) 442 except ValueError as e: 443 raise ParseError('Failed to parse {0} field: {1}.'.format(name, e)) 444 except TypeError as e: 445 raise ParseError('Failed to parse {0} field: {1}.'.format(name, e)) 446 447 def _ConvertAnyMessage(self, value, message): 448 """Convert a JSON representation into Any message.""" 449 if isinstance(value, dict) and not value: 450 return 451 try: 452 type_url = value['@type'] 453 except KeyError: 454 raise ParseError('@type is missing when parsing any message.') 455 456 sub_message = _CreateMessageFromTypeUrl(type_url) 457 message_descriptor = sub_message.DESCRIPTOR 458 full_name = message_descriptor.full_name 459 if _IsWrapperMessage(message_descriptor): 460 self._ConvertWrapperMessage(value['value'], sub_message) 461 elif full_name in _WKTJSONMETHODS: 462 methodcaller( 463 _WKTJSONMETHODS[full_name][1], value['value'], sub_message)(self) 464 else: 465 del value['@type'] 466 self._ConvertFieldValuePair(value, sub_message) 467 # Sets Any message 468 message.value = sub_message.SerializeToString() 469 message.type_url = type_url 470 471 def _ConvertGenericMessage(self, value, message): 472 """Convert a JSON representation into message with FromJsonString.""" 473 # Durantion, Timestamp, FieldMask have FromJsonString method to do the 474 # convert. Users can also call the method directly. 475 message.FromJsonString(value) 476 477 def _ConvertValueMessage(self, value, message): 478 """Convert a JSON representation into Value message.""" 479 if isinstance(value, dict): 480 self._ConvertStructMessage(value, message.struct_value) 481 elif isinstance(value, list): 482 self. _ConvertListValueMessage(value, message.list_value) 483 elif value is None: 484 message.null_value = 0 485 elif isinstance(value, bool): 486 message.bool_value = value 487 elif isinstance(value, six.string_types): 488 message.string_value = value 489 elif isinstance(value, _INT_OR_FLOAT): 490 message.number_value = value 491 else: 492 raise ParseError('Unexpected type for Value message.') 493 494 def _ConvertListValueMessage(self, value, message): 495 """Convert a JSON representation into ListValue message.""" 496 if not isinstance(value, list): 497 raise ParseError( 498 'ListValue must be in [] which is {0}.'.format(value)) 499 message.ClearField('values') 500 for item in value: 501 self._ConvertValueMessage(item, message.values.add()) 502 503 def _ConvertStructMessage(self, value, message): 504 """Convert a JSON representation into Struct message.""" 505 if not isinstance(value, dict): 506 raise ParseError( 507 'Struct must be in a dict which is {0}.'.format(value)) 508 for key in value: 509 self._ConvertValueMessage(value[key], message.fields[key]) 510 return 511 512 def _ConvertWrapperMessage(self, value, message): 513 """Convert a JSON representation into Wrapper message.""" 514 field = message.DESCRIPTOR.fields_by_name['value'] 515 setattr(message, 'value', _ConvertScalarFieldValue(value, field)) 516 517 def _ConvertMapFieldValue(self, value, message, field): 518 """Convert map field value for a message map field. 519 520 Args: 521 value: A JSON object to convert the map field value. 522 message: A protocol message to record the converted data. 523 field: The descriptor of the map field to be converted. 524 525 Raises: 526 ParseError: In case of convert problems. 527 """ 528 if not isinstance(value, dict): 529 raise ParseError( 530 'Map field {0} must be in a dict which is {1}.'.format( 531 field.name, value)) 532 key_field = field.message_type.fields_by_name['key'] 533 value_field = field.message_type.fields_by_name['value'] 534 for key in value: 535 key_value = _ConvertScalarFieldValue(key, key_field, True) 536 if value_field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: 537 self.ConvertMessage(value[key], getattr( 538 message, field.name)[key_value]) 539 else: 540 getattr(message, field.name)[key_value] = _ConvertScalarFieldValue( 541 value[key], value_field) 542 543 544def _ConvertScalarFieldValue(value, field, require_str=False): 545 """Convert a single scalar field value. 546 547 Args: 548 value: A scalar value to convert the scalar field value. 549 field: The descriptor of the field to convert. 550 require_str: If True, the field value must be a str. 551 552 Returns: 553 The converted scalar field value 554 555 Raises: 556 ParseError: In case of convert problems. 557 """ 558 if field.cpp_type in _INT_TYPES: 559 return _ConvertInteger(value) 560 elif field.cpp_type in _FLOAT_TYPES: 561 return _ConvertFloat(value) 562 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL: 563 return _ConvertBool(value, require_str) 564 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING: 565 if field.type == descriptor.FieldDescriptor.TYPE_BYTES: 566 return base64.b64decode(value) 567 else: 568 # Checking for unpaired surrogates appears to be unreliable, 569 # depending on the specific Python version, so we check manually. 570 if _UNPAIRED_SURROGATE_PATTERN.search(value): 571 raise ParseError('Unpaired surrogate') 572 return value 573 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM: 574 # Convert an enum value. 575 enum_value = field.enum_type.values_by_name.get(value, None) 576 if enum_value is None: 577 raise ParseError( 578 'Enum value must be a string literal with double quotes. ' 579 'Type "{0}" has no value named {1}.'.format( 580 field.enum_type.full_name, value)) 581 return enum_value.number 582 583 584def _ConvertInteger(value): 585 """Convert an integer. 586 587 Args: 588 value: A scalar value to convert. 589 590 Returns: 591 The integer value. 592 593 Raises: 594 ParseError: If an integer couldn't be consumed. 595 """ 596 if isinstance(value, float): 597 raise ParseError('Couldn\'t parse integer: {0}.'.format(value)) 598 599 if isinstance(value, six.text_type) and value.find(' ') != -1: 600 raise ParseError('Couldn\'t parse integer: "{0}".'.format(value)) 601 602 return int(value) 603 604 605def _ConvertFloat(value): 606 """Convert an floating point number.""" 607 if value == 'nan': 608 raise ParseError('Couldn\'t parse float "nan", use "NaN" instead.') 609 try: 610 # Assume Python compatible syntax. 611 return float(value) 612 except ValueError: 613 # Check alternative spellings. 614 if value == _NEG_INFINITY: 615 return float('-inf') 616 elif value == _INFINITY: 617 return float('inf') 618 elif value == _NAN: 619 return float('nan') 620 else: 621 raise ParseError('Couldn\'t parse float: {0}.'.format(value)) 622 623 624def _ConvertBool(value, require_str): 625 """Convert a boolean value. 626 627 Args: 628 value: A scalar value to convert. 629 require_str: If True, value must be a str. 630 631 Returns: 632 The bool parsed. 633 634 Raises: 635 ParseError: If a boolean value couldn't be consumed. 636 """ 637 if require_str: 638 if value == 'true': 639 return True 640 elif value == 'false': 641 return False 642 else: 643 raise ParseError('Expected "true" or "false", not {0}.'.format(value)) 644 645 if not isinstance(value, bool): 646 raise ParseError('Expected true or false without quotes.') 647 return value 648 649_WKTJSONMETHODS = { 650 'google.protobuf.Any': ['_AnyMessageToJsonObject', 651 '_ConvertAnyMessage'], 652 'google.protobuf.Duration': ['_GenericMessageToJsonObject', 653 '_ConvertGenericMessage'], 654 'google.protobuf.FieldMask': ['_GenericMessageToJsonObject', 655 '_ConvertGenericMessage'], 656 'google.protobuf.ListValue': ['_ListValueMessageToJsonObject', 657 '_ConvertListValueMessage'], 658 'google.protobuf.Struct': ['_StructMessageToJsonObject', 659 '_ConvertStructMessage'], 660 'google.protobuf.Timestamp': ['_GenericMessageToJsonObject', 661 '_ConvertGenericMessage'], 662 'google.protobuf.Value': ['_ValueMessageToJsonObject', 663 '_ConvertValueMessage'] 664} 665