OLD | NEW |
(Empty) | |
| 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 from six import text_type |
| 49 import sys |
| 50 |
| 51 from google.protobuf import descriptor |
| 52 |
| 53 _TIMESTAMPFOMAT = '%Y-%m-%dT%H:%M:%S' |
| 54 _INT_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_INT32, |
| 55 descriptor.FieldDescriptor.CPPTYPE_UINT32, |
| 56 descriptor.FieldDescriptor.CPPTYPE_INT64, |
| 57 descriptor.FieldDescriptor.CPPTYPE_UINT64]) |
| 58 _INT64_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_INT64, |
| 59 descriptor.FieldDescriptor.CPPTYPE_UINT64]) |
| 60 _FLOAT_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_FLOAT, |
| 61 descriptor.FieldDescriptor.CPPTYPE_DOUBLE]) |
| 62 _INFINITY = 'Infinity' |
| 63 _NEG_INFINITY = '-Infinity' |
| 64 _NAN = 'NaN' |
| 65 |
| 66 |
| 67 class Error(Exception): |
| 68 """Top-level module error for json_format.""" |
| 69 |
| 70 |
| 71 class SerializeToJsonError(Error): |
| 72 """Thrown if serialization to JSON fails.""" |
| 73 |
| 74 |
| 75 class ParseError(Error): |
| 76 """Thrown in case of parsing error.""" |
| 77 |
| 78 |
| 79 def MessageToJson(message, including_default_value_fields=False): |
| 80 """Converts protobuf message to JSON format. |
| 81 |
| 82 Args: |
| 83 message: The protocol buffers message instance to serialize. |
| 84 including_default_value_fields: If True, singular primitive fields, |
| 85 repeated fields, and map fields will always be serialized. If |
| 86 False, only serialize non-empty fields. Singular message fields |
| 87 and oneof fields are not affected by this option. |
| 88 |
| 89 Returns: |
| 90 A string containing the JSON formatted protocol buffer message. |
| 91 """ |
| 92 js = _MessageToJsonObject(message, including_default_value_fields) |
| 93 return json.dumps(js, indent=2) |
| 94 |
| 95 |
| 96 def _MessageToJsonObject(message, including_default_value_fields): |
| 97 """Converts message to an object according to Proto3 JSON Specification.""" |
| 98 message_descriptor = message.DESCRIPTOR |
| 99 if hasattr(message, 'ToJsonString'): |
| 100 return message.ToJsonString() |
| 101 if _IsWrapperMessage(message_descriptor): |
| 102 return _WrapperMessageToJsonObject(message) |
| 103 return _RegularMessageToJsonObject(message, including_default_value_fields) |
| 104 |
| 105 |
| 106 def _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 |
| 112 def _RegularMessageToJsonObject(message, including_default_value_fields): |
| 113 """Converts normal message according to Proto3 JSON Specification.""" |
| 114 js = {} |
| 115 fields = message.ListFields() |
| 116 include_default = including_default_value_fields |
| 117 |
| 118 try: |
| 119 for field, value in fields: |
| 120 name = field.camelcase_name |
| 121 if _IsMapEntry(field): |
| 122 # Convert a map field. |
| 123 v_field = field.message_type.fields_by_name['value'] |
| 124 js_map = {} |
| 125 for key in value: |
| 126 if isinstance(key, bool): |
| 127 if key: |
| 128 recorded_key = 'true' |
| 129 else: |
| 130 recorded_key = 'false' |
| 131 else: |
| 132 recorded_key = key |
| 133 js_map[recorded_key] = _FieldToJsonObject( |
| 134 v_field, value[key], including_default_value_fields) |
| 135 js[name] = js_map |
| 136 elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: |
| 137 # Convert a repeated field. |
| 138 js[name] = [_FieldToJsonObject(field, k, include_default) |
| 139 for k in value] |
| 140 else: |
| 141 js[name] = _FieldToJsonObject(field, value, include_default) |
| 142 |
| 143 # Serialize default value if including_default_value_fields is True. |
| 144 if including_default_value_fields: |
| 145 message_descriptor = message.DESCRIPTOR |
| 146 for field in message_descriptor.fields: |
| 147 # Singular message fields and oneof fields will not be affected. |
| 148 if ((field.label != descriptor.FieldDescriptor.LABEL_REPEATED and |
| 149 field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE) or |
| 150 field.containing_oneof): |
| 151 continue |
| 152 name = field.camelcase_name |
| 153 if name in js: |
| 154 # Skip the field which has been serailized already. |
| 155 continue |
| 156 if _IsMapEntry(field): |
| 157 js[name] = {} |
| 158 elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: |
| 159 js[name] = [] |
| 160 else: |
| 161 js[name] = _FieldToJsonObject(field, field.default_value) |
| 162 |
| 163 except ValueError as e: |
| 164 raise SerializeToJsonError( |
| 165 'Failed to serialize {0} field: {1}.'.format(field.name, e)) |
| 166 |
| 167 return js |
| 168 |
| 169 |
| 170 def _FieldToJsonObject( |
| 171 field, value, including_default_value_fields=False): |
| 172 """Converts field value according to Proto3 JSON Specification.""" |
| 173 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: |
| 174 return _MessageToJsonObject(value, including_default_value_fields) |
| 175 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM: |
| 176 enum_value = field.enum_type.values_by_number.get(value, None) |
| 177 if enum_value is not None: |
| 178 return enum_value.name |
| 179 else: |
| 180 raise SerializeToJsonError('Enum field contains an integer value ' |
| 181 'which can not mapped to an enum value.') |
| 182 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING: |
| 183 if field.type == descriptor.FieldDescriptor.TYPE_BYTES: |
| 184 # Use base64 Data encoding for bytes |
| 185 return base64.b64encode(value).decode('utf-8') |
| 186 else: |
| 187 return value |
| 188 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL: |
| 189 return bool(value) |
| 190 elif field.cpp_type in _INT64_TYPES: |
| 191 return str(value) |
| 192 elif field.cpp_type in _FLOAT_TYPES: |
| 193 if math.isinf(value): |
| 194 if value < 0.0: |
| 195 return _NEG_INFINITY |
| 196 else: |
| 197 return _INFINITY |
| 198 if math.isnan(value): |
| 199 return _NAN |
| 200 return value |
| 201 |
| 202 |
| 203 def _IsWrapperMessage(message_descriptor): |
| 204 return message_descriptor.file.name == 'google/protobuf/wrappers.proto' |
| 205 |
| 206 |
| 207 def _WrapperMessageToJsonObject(message): |
| 208 return _FieldToJsonObject( |
| 209 message.DESCRIPTOR.fields_by_name['value'], message.value) |
| 210 |
| 211 |
| 212 def _DuplicateChecker(js): |
| 213 result = {} |
| 214 for name, value in js: |
| 215 if name in result: |
| 216 raise ParseError('Failed to load JSON: duplicate key {0}.'.format(name)) |
| 217 result[name] = value |
| 218 return result |
| 219 |
| 220 |
| 221 def Parse(text, message): |
| 222 """Parses a JSON representation of a protocol message into a message. |
| 223 |
| 224 Args: |
| 225 text: Message JSON representation. |
| 226 message: A protocol beffer message to merge into. |
| 227 |
| 228 Returns: |
| 229 The same message passed as argument. |
| 230 |
| 231 Raises:: |
| 232 ParseError: On JSON parsing problems. |
| 233 """ |
| 234 if not isinstance(text, text_type): text = text.decode('utf-8') |
| 235 try: |
| 236 if sys.version_info < (2, 7): |
| 237 # object_pair_hook is not supported before python2.7 |
| 238 js = json.loads(text) |
| 239 else: |
| 240 js = json.loads(text, object_pairs_hook=_DuplicateChecker) |
| 241 except ValueError as e: |
| 242 raise ParseError('Failed to load JSON: {0}.'.format(str(e))) |
| 243 _ConvertFieldValuePair(js, message) |
| 244 return message |
| 245 |
| 246 |
| 247 def _ConvertFieldValuePair(js, message): |
| 248 """Convert field value pairs into regular message. |
| 249 |
| 250 Args: |
| 251 js: A JSON object to convert the field value pairs. |
| 252 message: A regular protocol message to record the data. |
| 253 |
| 254 Raises: |
| 255 ParseError: In case of problems converting. |
| 256 """ |
| 257 names = [] |
| 258 message_descriptor = message.DESCRIPTOR |
| 259 for name in js: |
| 260 try: |
| 261 field = message_descriptor.fields_by_camelcase_name.get(name, None) |
| 262 if not field: |
| 263 raise ParseError( |
| 264 'Message type "{0}" has no field named "{1}".'.format( |
| 265 message_descriptor.full_name, name)) |
| 266 if name in names: |
| 267 raise ParseError( |
| 268 'Message type "{0}" should not have multiple "{1}" fields.'.format( |
| 269 message.DESCRIPTOR.full_name, name)) |
| 270 names.append(name) |
| 271 # Check no other oneof field is parsed. |
| 272 if field.containing_oneof is not None: |
| 273 oneof_name = field.containing_oneof.name |
| 274 if oneof_name in names: |
| 275 raise ParseError('Message type "{0}" should not have multiple "{1}" ' |
| 276 'oneof fields.'.format( |
| 277 message.DESCRIPTOR.full_name, oneof_name)) |
| 278 names.append(oneof_name) |
| 279 |
| 280 value = js[name] |
| 281 if value is None: |
| 282 message.ClearField(field.name) |
| 283 continue |
| 284 |
| 285 # Parse field value. |
| 286 if _IsMapEntry(field): |
| 287 message.ClearField(field.name) |
| 288 _ConvertMapFieldValue(value, message, field) |
| 289 elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: |
| 290 message.ClearField(field.name) |
| 291 if not isinstance(value, list): |
| 292 raise ParseError('repeated field {0} must be in [] which is ' |
| 293 '{1}.'.format(name, value)) |
| 294 for item in value: |
| 295 if item is None: |
| 296 continue |
| 297 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: |
| 298 sub_message = getattr(message, field.name).add() |
| 299 _ConvertMessage(item, sub_message) |
| 300 else: |
| 301 getattr(message, field.name).append( |
| 302 _ConvertScalarFieldValue(item, field)) |
| 303 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: |
| 304 sub_message = getattr(message, field.name) |
| 305 _ConvertMessage(value, sub_message) |
| 306 else: |
| 307 setattr(message, field.name, _ConvertScalarFieldValue(value, field)) |
| 308 except ParseError as e: |
| 309 if field and field.containing_oneof is None: |
| 310 raise ParseError('Failed to parse {0} field: {1}'.format(name, e)) |
| 311 else: |
| 312 raise ParseError(str(e)) |
| 313 except ValueError as e: |
| 314 raise ParseError('Failed to parse {0} field: {1}.'.format(name, e)) |
| 315 except TypeError as e: |
| 316 raise ParseError('Failed to parse {0} field: {1}.'.format(name, e)) |
| 317 |
| 318 |
| 319 def _ConvertMessage(value, message): |
| 320 """Convert a JSON object into a message. |
| 321 |
| 322 Args: |
| 323 value: A JSON object. |
| 324 message: A WKT or regular protocol message to record the data. |
| 325 |
| 326 Raises: |
| 327 ParseError: In case of convert problems. |
| 328 """ |
| 329 message_descriptor = message.DESCRIPTOR |
| 330 if hasattr(message, 'FromJsonString'): |
| 331 message.FromJsonString(value) |
| 332 elif _IsWrapperMessage(message_descriptor): |
| 333 _ConvertWrapperMessage(value, message) |
| 334 else: |
| 335 _ConvertFieldValuePair(value, message) |
| 336 |
| 337 def _ConvertWrapperMessage(value, message): |
| 338 """Convert a JSON representation into Wrapper message.""" |
| 339 field = message.DESCRIPTOR.fields_by_name['value'] |
| 340 setattr(message, 'value', _ConvertScalarFieldValue(value, field)) |
| 341 |
| 342 |
| 343 def _ConvertMapFieldValue(value, message, field): |
| 344 """Convert map field value for a message map field. |
| 345 |
| 346 Args: |
| 347 value: A JSON object to convert the map field value. |
| 348 message: A protocol message to record the converted data. |
| 349 field: The descriptor of the map field to be converted. |
| 350 |
| 351 Raises: |
| 352 ParseError: In case of convert problems. |
| 353 """ |
| 354 if not isinstance(value, dict): |
| 355 raise ParseError( |
| 356 'Map fieled {0} must be in {} which is {1}.'.format(field.name, value)) |
| 357 key_field = field.message_type.fields_by_name['key'] |
| 358 value_field = field.message_type.fields_by_name['value'] |
| 359 for key in value: |
| 360 key_value = _ConvertScalarFieldValue(key, key_field, True) |
| 361 if value_field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: |
| 362 _ConvertMessage(value[key], getattr(message, field.name)[key_value]) |
| 363 else: |
| 364 getattr(message, field.name)[key_value] = _ConvertScalarFieldValue( |
| 365 value[key], value_field) |
| 366 |
| 367 |
| 368 def _ConvertScalarFieldValue(value, field, require_str=False): |
| 369 """Convert a single scalar field value. |
| 370 |
| 371 Args: |
| 372 value: A scalar value to convert the scalar field value. |
| 373 field: The descriptor of the field to convert. |
| 374 require_str: If True, the field value must be a str. |
| 375 |
| 376 Returns: |
| 377 The converted scalar field value |
| 378 |
| 379 Raises: |
| 380 ParseError: In case of convert problems. |
| 381 """ |
| 382 if field.cpp_type in _INT_TYPES: |
| 383 return _ConvertInteger(value) |
| 384 elif field.cpp_type in _FLOAT_TYPES: |
| 385 return _ConvertFloat(value) |
| 386 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL: |
| 387 return _ConvertBool(value, require_str) |
| 388 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING: |
| 389 if field.type == descriptor.FieldDescriptor.TYPE_BYTES: |
| 390 return base64.b64decode(value) |
| 391 else: |
| 392 return value |
| 393 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM: |
| 394 # Convert an enum value. |
| 395 enum_value = field.enum_type.values_by_name.get(value, None) |
| 396 if enum_value is None: |
| 397 raise ParseError( |
| 398 'Enum value must be a string literal with double quotes. ' |
| 399 'Type "{0}" has no value named {1}.'.format( |
| 400 field.enum_type.full_name, value)) |
| 401 return enum_value.number |
| 402 |
| 403 |
| 404 def _ConvertInteger(value): |
| 405 """Convert an integer. |
| 406 |
| 407 Args: |
| 408 value: A scalar value to convert. |
| 409 |
| 410 Returns: |
| 411 The integer value. |
| 412 |
| 413 Raises: |
| 414 ParseError: If an integer couldn't be consumed. |
| 415 """ |
| 416 if isinstance(value, float): |
| 417 raise ParseError('Couldn\'t parse integer: {0}.'.format(value)) |
| 418 |
| 419 if isinstance(value, text_type) and value.find(' ') != -1: |
| 420 raise ParseError('Couldn\'t parse integer: "{0}".'.format(value)) |
| 421 |
| 422 return int(value) |
| 423 |
| 424 |
| 425 def _ConvertFloat(value): |
| 426 """Convert an floating point number.""" |
| 427 if value == 'nan': |
| 428 raise ParseError('Couldn\'t parse float "nan", use "NaN" instead.') |
| 429 try: |
| 430 # Assume Python compatible syntax. |
| 431 return float(value) |
| 432 except ValueError: |
| 433 # Check alternative spellings. |
| 434 if value == _NEG_INFINITY: |
| 435 return float('-inf') |
| 436 elif value == _INFINITY: |
| 437 return float('inf') |
| 438 elif value == _NAN: |
| 439 return float('nan') |
| 440 else: |
| 441 raise ParseError('Couldn\'t parse float: {0}.'.format(value)) |
| 442 |
| 443 |
| 444 def _ConvertBool(value, require_str): |
| 445 """Convert a boolean value. |
| 446 |
| 447 Args: |
| 448 value: A scalar value to convert. |
| 449 require_str: If True, value must be a str. |
| 450 |
| 451 Returns: |
| 452 The bool parsed. |
| 453 |
| 454 Raises: |
| 455 ParseError: If a boolean value couldn't be consumed. |
| 456 """ |
| 457 if require_str: |
| 458 if value == 'true': |
| 459 return True |
| 460 elif value == 'false': |
| 461 return False |
| 462 else: |
| 463 raise ParseError('Expected "true" or "false", not {0}.'.format(value)) |
| 464 |
| 465 if not isinstance(value, bool): |
| 466 raise ParseError('Expected true or false without quotes.') |
| 467 return value |
OLD | NEW |