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