1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc. All rights reserved.
3#
4# Use of this source code is governed by a BSD-style
5# license that can be found in the LICENSE file or at
6# https://developers.google.com/open-source/licenses/bsd
7
8"""Contains routines for printing protocol messages in JSON format.
9
10Simple usage example:
11
12 # Create a proto object and serialize it to a json format string.
13 message = my_proto_pb2.MyMessage(foo='bar')
14 json_string = json_format.MessageToJson(message)
15
16 # Parse a json format string to proto object.
17 message = json_format.Parse(json_string, my_proto_pb2.MyMessage())
18"""
19
20__author__ = 'jieluo@google.com (Jie Luo)'
21
22
23import base64
24from collections import OrderedDict
25import json
26import math
27from operator import methodcaller
28import re
29
30from google.protobuf import descriptor
31from google.protobuf import message_factory
32from google.protobuf import symbol_database
33from google.protobuf.internal import type_checkers
34
35
36_INT_TYPES = frozenset([
37 descriptor.FieldDescriptor.CPPTYPE_INT32,
38 descriptor.FieldDescriptor.CPPTYPE_UINT32,
39 descriptor.FieldDescriptor.CPPTYPE_INT64,
40 descriptor.FieldDescriptor.CPPTYPE_UINT64,
41])
42_INT64_TYPES = frozenset([
43 descriptor.FieldDescriptor.CPPTYPE_INT64,
44 descriptor.FieldDescriptor.CPPTYPE_UINT64,
45])
46_FLOAT_TYPES = frozenset([
47 descriptor.FieldDescriptor.CPPTYPE_FLOAT,
48 descriptor.FieldDescriptor.CPPTYPE_DOUBLE,
49])
50_INFINITY = 'Infinity'
51_NEG_INFINITY = '-Infinity'
52_NAN = 'NaN'
53
54_UNPAIRED_SURROGATE_PATTERN = re.compile(
55 '[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]'
56)
57
58_VALID_EXTENSION_NAME = re.compile(r'\[[a-zA-Z0-9\._]*\]$')
59
60
61class Error(Exception):
62 """Top-level module error for json_format."""
63
64
65class SerializeToJsonError(Error):
66 """Thrown if serialization to JSON fails."""
67
68
69class ParseError(Error):
70 """Thrown in case of parsing error."""
71
72
73class EnumStringValueParseError(ParseError):
74 """Thrown if unknown string enum value is encountered.
75
76 This exception is suppressed if ignore_unknown_fields is set.
77 """
78
79
80def MessageToJson(
81 message,
82 preserving_proto_field_name=False,
83 indent=2,
84 sort_keys=False,
85 use_integers_for_enums=False,
86 descriptor_pool=None,
87 float_precision=None,
88 ensure_ascii=True,
89 always_print_fields_with_no_presence=False,
90):
91 """Converts protobuf message to JSON format.
92
93 Args:
94 message: The protocol buffers message instance to serialize.
95 always_print_fields_with_no_presence: If True, fields without presence
96 (implicit presence scalars, repeated fields, and map fields) will always
97 be serialized. Any field that supports presence is not affected by this
98 option (including singular message fields and oneof fields).
99 preserving_proto_field_name: If True, use the original proto field names as
100 defined in the .proto file. If False, convert the field names to
101 lowerCamelCase.
102 indent: The JSON object will be pretty-printed with this indent level. An
103 indent level of 0 or negative will only insert newlines. If the indent
104 level is None, no newlines will be inserted.
105 sort_keys: If True, then the output will be sorted by field names.
106 use_integers_for_enums: If true, print integers instead of enum names.
107 descriptor_pool: A Descriptor Pool for resolving types. If None use the
108 default.
109 float_precision: Deprecated. If set, use this to specify float field valid
110 digits.
111 ensure_ascii: If True, strings with non-ASCII characters are escaped. If
112 False, Unicode strings are returned unchanged.
113
114 Returns:
115 A string containing the JSON formatted protocol buffer message.
116 """
117 printer = _Printer(
118 preserving_proto_field_name,
119 use_integers_for_enums,
120 descriptor_pool,
121 float_precision,
122 always_print_fields_with_no_presence,
123 )
124 return printer.ToJsonString(message, indent, sort_keys, ensure_ascii)
125
126
127def MessageToDict(
128 message,
129 always_print_fields_with_no_presence=False,
130 preserving_proto_field_name=False,
131 use_integers_for_enums=False,
132 descriptor_pool=None,
133 float_precision=None,
134):
135 """Converts protobuf message to a dictionary.
136
137 When the dictionary is encoded to JSON, it conforms to proto3 JSON spec.
138
139 Args:
140 message: The protocol buffers message instance to serialize.
141 always_print_fields_with_no_presence: If True, fields without presence
142 (implicit presence scalars, repeated fields, and map fields) will always
143 be serialized. Any field that supports presence is not affected by this
144 option (including singular message fields and oneof fields).
145 preserving_proto_field_name: If True, use the original proto field names as
146 defined in the .proto file. If False, convert the field names to
147 lowerCamelCase.
148 use_integers_for_enums: If true, print integers instead of enum names.
149 descriptor_pool: A Descriptor Pool for resolving types. If None use the
150 default.
151 float_precision: Deprecated. If set, use this to specify float field valid
152 digits.
153
154 Returns:
155 A dict representation of the protocol buffer message.
156 """
157 printer = _Printer(
158 preserving_proto_field_name,
159 use_integers_for_enums,
160 descriptor_pool,
161 float_precision,
162 always_print_fields_with_no_presence,
163 )
164 # pylint: disable=protected-access
165 return printer._MessageToJsonObject(message)
166
167
168def _IsMapEntry(field):
169 return (
170 field.type == descriptor.FieldDescriptor.TYPE_MESSAGE
171 and field.message_type.has_options
172 and field.message_type.GetOptions().map_entry
173 )
174
175
176class _Printer(object):
177 """JSON format printer for protocol message."""
178
179 def __init__(
180 self,
181 preserving_proto_field_name=False,
182 use_integers_for_enums=False,
183 descriptor_pool=None,
184 float_precision=None,
185 always_print_fields_with_no_presence=False,
186 ):
187 self.always_print_fields_with_no_presence = (
188 always_print_fields_with_no_presence
189 )
190 self.preserving_proto_field_name = preserving_proto_field_name
191 self.use_integers_for_enums = use_integers_for_enums
192 self.descriptor_pool = descriptor_pool
193 if float_precision:
194 self.float_format = '.{}g'.format(float_precision)
195 else:
196 self.float_format = None
197
198 def ToJsonString(self, message, indent, sort_keys, ensure_ascii):
199 js = self._MessageToJsonObject(message)
200 return json.dumps(
201 js, indent=indent, sort_keys=sort_keys, ensure_ascii=ensure_ascii
202 )
203
204 def _MessageToJsonObject(self, message):
205 """Converts message to an object according to Proto3 JSON Specification."""
206 message_descriptor = message.DESCRIPTOR
207 full_name = message_descriptor.full_name
208 if _IsWrapperMessage(message_descriptor):
209 return self._WrapperMessageToJsonObject(message)
210 if full_name in _WKTJSONMETHODS:
211 return methodcaller(_WKTJSONMETHODS[full_name][0], message)(self)
212 js = {}
213 return self._RegularMessageToJsonObject(message, js)
214
215 def _RegularMessageToJsonObject(self, message, js):
216 """Converts normal message according to Proto3 JSON Specification."""
217 fields = message.ListFields()
218
219 try:
220 for field, value in fields:
221 if self.preserving_proto_field_name:
222 name = field.name
223 else:
224 name = field.json_name
225 if _IsMapEntry(field):
226 # Convert a map field.
227 v_field = field.message_type.fields_by_name['value']
228 js_map = {}
229 for key in value:
230 if isinstance(key, bool):
231 if key:
232 recorded_key = 'true'
233 else:
234 recorded_key = 'false'
235 else:
236 recorded_key = str(key)
237 js_map[recorded_key] = self._FieldToJsonObject(v_field, value[key])
238 js[name] = js_map
239 elif field.is_repeated:
240 # Convert a repeated field.
241 js[name] = [self._FieldToJsonObject(field, k) for k in value]
242 elif field.is_extension:
243 name = '[%s]' % field.full_name
244 js[name] = self._FieldToJsonObject(field, value)
245 else:
246 js[name] = self._FieldToJsonObject(field, value)
247
248 # Serialize default value if including_default_value_fields is True.
249 if (
250 self.always_print_fields_with_no_presence
251 ):
252 message_descriptor = message.DESCRIPTOR
253 for field in message_descriptor.fields:
254
255 # always_print_fields_with_no_presence doesn't apply to
256 # any field which supports presence.
257 if self.always_print_fields_with_no_presence and field.has_presence:
258 continue
259
260 if self.preserving_proto_field_name:
261 name = field.name
262 else:
263 name = field.json_name
264 if name in js:
265 # Skip the field which has been serialized already.
266 continue
267 if _IsMapEntry(field):
268 js[name] = {}
269 elif field.is_repeated:
270 js[name] = []
271 else:
272 js[name] = self._FieldToJsonObject(field, field.default_value)
273
274 except ValueError as e:
275 raise SerializeToJsonError(
276 'Failed to serialize {0} field: {1}.'.format(field.name, e)
277 ) from e
278
279 return js
280
281 def _FieldToJsonObject(self, field, value):
282 """Converts field value according to Proto3 JSON Specification."""
283 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
284 return self._MessageToJsonObject(value)
285 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM:
286 if self.use_integers_for_enums:
287 return value
288 if field.enum_type.full_name == 'google.protobuf.NullValue':
289 return None
290 enum_value = field.enum_type.values_by_number.get(value, None)
291 if enum_value is not None:
292 return enum_value.name
293 else:
294 if field.enum_type.is_closed:
295 raise SerializeToJsonError(
296 'Enum field contains an integer value '
297 'which can not mapped to an enum value.'
298 )
299 else:
300 return value
301 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING:
302 if field.type == descriptor.FieldDescriptor.TYPE_BYTES:
303 # Use base64 Data encoding for bytes
304 return base64.b64encode(value).decode('utf-8')
305 else:
306 return str(value)
307 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL:
308 return bool(value)
309 elif field.cpp_type in _INT64_TYPES:
310 return str(value)
311 elif field.cpp_type in _FLOAT_TYPES:
312 if math.isinf(value):
313 if value < 0.0:
314 return _NEG_INFINITY
315 else:
316 return _INFINITY
317 if math.isnan(value):
318 return _NAN
319 if self.float_format:
320 return float(format(value, self.float_format))
321 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_FLOAT:
322 return type_checkers.ToShortestFloat(value)
323
324 return value
325
326 def _AnyMessageToJsonObject(self, message):
327 """Converts Any message according to Proto3 JSON Specification."""
328 if not message.ListFields():
329 return {}
330 # Must print @type first, use OrderedDict instead of {}
331 js = OrderedDict()
332 type_url = message.type_url
333 js['@type'] = type_url
334 sub_message = _CreateMessageFromTypeUrl(type_url, self.descriptor_pool)
335 sub_message.ParseFromString(message.value)
336 message_descriptor = sub_message.DESCRIPTOR
337 full_name = message_descriptor.full_name
338 if _IsWrapperMessage(message_descriptor):
339 js['value'] = self._WrapperMessageToJsonObject(sub_message)
340 return js
341 if full_name in _WKTJSONMETHODS:
342 js['value'] = methodcaller(_WKTJSONMETHODS[full_name][0], sub_message)(
343 self
344 )
345 return js
346 return self._RegularMessageToJsonObject(sub_message, js)
347
348 def _GenericMessageToJsonObject(self, message):
349 """Converts message according to Proto3 JSON Specification."""
350 # Duration, Timestamp and FieldMask have ToJsonString method to do the
351 # convert. Users can also call the method directly.
352 return message.ToJsonString()
353
354 def _ValueMessageToJsonObject(self, message):
355 """Converts Value message according to Proto3 JSON Specification."""
356 which = message.WhichOneof('kind')
357 # If the Value message is not set treat as null_value when serialize
358 # to JSON. The parse back result will be different from original message.
359 if which is None or which == 'null_value':
360 return None
361 if which == 'list_value':
362 return self._ListValueMessageToJsonObject(message.list_value)
363 if which == 'number_value':
364 value = message.number_value
365 if math.isinf(value):
366 raise ValueError(
367 'Fail to serialize Infinity for Value.number_value, '
368 'which would parse as string_value'
369 )
370 if math.isnan(value):
371 raise ValueError(
372 'Fail to serialize NaN for Value.number_value, '
373 'which would parse as string_value'
374 )
375 else:
376 value = getattr(message, which)
377 oneof_descriptor = message.DESCRIPTOR.fields_by_name[which]
378 return self._FieldToJsonObject(oneof_descriptor, value)
379
380 def _ListValueMessageToJsonObject(self, message):
381 """Converts ListValue message according to Proto3 JSON Specification."""
382 return [self._ValueMessageToJsonObject(value) for value in message.values]
383
384 def _StructMessageToJsonObject(self, message):
385 """Converts Struct message according to Proto3 JSON Specification."""
386 fields = message.fields
387 ret = {}
388 for key in fields:
389 ret[key] = self._ValueMessageToJsonObject(fields[key])
390 return ret
391
392 def _WrapperMessageToJsonObject(self, message):
393 return self._FieldToJsonObject(
394 message.DESCRIPTOR.fields_by_name['value'], message.value
395 )
396
397
398def _IsWrapperMessage(message_descriptor):
399 return message_descriptor.file.name == 'google/protobuf/wrappers.proto'
400
401
402def _DuplicateChecker(js):
403 result = {}
404 for name, value in js:
405 if name in result:
406 raise ParseError('Failed to load JSON: duplicate key {0}.'.format(name))
407 result[name] = value
408 return result
409
410
411def _CreateMessageFromTypeUrl(type_url, descriptor_pool):
412 """Creates a message from a type URL."""
413 db = symbol_database.Default()
414 pool = db.pool if descriptor_pool is None else descriptor_pool
415 type_name = type_url.split('/')[-1]
416 try:
417 message_descriptor = pool.FindMessageTypeByName(type_name)
418 except KeyError as e:
419 raise TypeError(
420 'Can not find message descriptor by type_url: {0}'.format(type_url)
421 ) from e
422 message_class = message_factory.GetMessageClass(message_descriptor)
423 return message_class()
424
425
426def Parse(
427 text,
428 message,
429 ignore_unknown_fields=False,
430 descriptor_pool=None,
431 max_recursion_depth=100,
432):
433 """Parses a JSON representation of a protocol message into a message.
434
435 Args:
436 text: Message JSON representation.
437 message: A protocol buffer message to merge into.
438 ignore_unknown_fields: If True, do not raise errors for unknown fields.
439 descriptor_pool: A Descriptor Pool for resolving types. If None use the
440 default.
441 max_recursion_depth: max recursion depth of JSON message to be deserialized.
442 JSON messages over this depth will fail to be deserialized. Default value
443 is 100.
444
445 Returns:
446 The same message passed as argument.
447
448 Raises::
449 ParseError: On JSON parsing problems.
450 """
451 if not isinstance(text, str):
452 text = text.decode('utf-8')
453
454 try:
455 js = json.loads(text, object_pairs_hook=_DuplicateChecker)
456 except Exception as e:
457 raise ParseError('Failed to load JSON: {0}.'.format(str(e))) from e
458
459 try:
460 return ParseDict(
461 js, message, ignore_unknown_fields, descriptor_pool, max_recursion_depth
462 )
463 except ParseError as e:
464 raise e
465 except Exception as e:
466 raise ParseError(
467 'Failed to parse JSON: {0}: {1}.'.format(type(e).__name__, str(e))
468 ) from e
469
470
471def ParseDict(
472 js_dict,
473 message,
474 ignore_unknown_fields=False,
475 descriptor_pool=None,
476 max_recursion_depth=100,
477):
478 """Parses a JSON dictionary representation into a message.
479
480 Args:
481 js_dict: Dict representation of a JSON message.
482 message: A protocol buffer message to merge into.
483 ignore_unknown_fields: If True, do not raise errors for unknown fields.
484 descriptor_pool: A Descriptor Pool for resolving types. If None use the
485 default.
486 max_recursion_depth: max recursion depth of JSON message to be deserialized.
487 JSON messages over this depth will fail to be deserialized. Default value
488 is 100.
489
490 Returns:
491 The same message passed as argument.
492 """
493 parser = _Parser(ignore_unknown_fields, descriptor_pool, max_recursion_depth)
494 parser.ConvertMessage(js_dict, message, '')
495 return message
496
497
498_INT_OR_FLOAT = (int, float)
499_LIST_LIKE = (list, tuple)
500
501
502class _Parser(object):
503 """JSON format parser for protocol message."""
504
505 def __init__(
506 self, ignore_unknown_fields, descriptor_pool, max_recursion_depth
507 ):
508 self.ignore_unknown_fields = ignore_unknown_fields
509 self.descriptor_pool = descriptor_pool
510 self.max_recursion_depth = max_recursion_depth
511 self.recursion_depth = 0
512
513 def ConvertMessage(self, value, message, path):
514 """Convert a JSON object into a message.
515
516 Args:
517 value: A JSON object.
518 message: A WKT or regular protocol message to record the data.
519 path: parent path to log parse error info.
520
521 Raises:
522 ParseError: In case of convert problems.
523 """
524 self.recursion_depth += 1
525 if self.recursion_depth > self.max_recursion_depth:
526 raise ParseError(
527 'Message too deep. Max recursion depth is {0}'.format(
528 self.max_recursion_depth
529 )
530 )
531 message_descriptor = message.DESCRIPTOR
532 full_name = message_descriptor.full_name
533 if not path:
534 path = message_descriptor.name
535 if _IsWrapperMessage(message_descriptor):
536 self._ConvertWrapperMessage(value, message, path)
537 elif full_name in _WKTJSONMETHODS:
538 methodcaller(_WKTJSONMETHODS[full_name][1], value, message, path)(self)
539 else:
540 self._ConvertFieldValuePair(value, message, path)
541 self.recursion_depth -= 1
542
543 def _ConvertFieldValuePair(self, js, message, path):
544 """Convert field value pairs into regular message.
545
546 Args:
547 js: A JSON object to convert the field value pairs.
548 message: A regular protocol message to record the data.
549 path: parent path to log parse error info.
550
551 Raises:
552 ParseError: In case of problems converting.
553 """
554 names = []
555 message_descriptor = message.DESCRIPTOR
556 fields_by_json_name = dict(
557 (f.json_name, f) for f in message_descriptor.fields
558 )
559 for name in js:
560 try:
561 field = fields_by_json_name.get(name, None)
562 if not field:
563 field = message_descriptor.fields_by_name.get(name, None)
564 if not field and _VALID_EXTENSION_NAME.match(name):
565 if not message_descriptor.is_extendable:
566 raise ParseError(
567 'Message type {0} does not have extensions at {1}'.format(
568 message_descriptor.full_name, path
569 )
570 )
571 identifier = name[1:-1] # strip [] brackets
572 # pylint: disable=protected-access
573 field = message.Extensions._FindExtensionByName(identifier)
574 # pylint: enable=protected-access
575 if not field:
576 # Try looking for extension by the message type name, dropping the
577 # field name following the final . separator in full_name.
578 identifier = '.'.join(identifier.split('.')[:-1])
579 # pylint: disable=protected-access
580 field = message.Extensions._FindExtensionByName(identifier)
581 # pylint: enable=protected-access
582 if not field:
583 if self.ignore_unknown_fields:
584 continue
585 raise ParseError(
586 (
587 'Message type "{0}" has no field named "{1}" at "{2}".\n'
588 ' Available Fields(except extensions): "{3}"'
589 ).format(
590 message_descriptor.full_name,
591 name,
592 path,
593 [f.json_name for f in message_descriptor.fields],
594 )
595 )
596 if name in names:
597 raise ParseError(
598 'Message type "{0}" should not have multiple '
599 '"{1}" fields at "{2}".'.format(
600 message.DESCRIPTOR.full_name, name, path
601 )
602 )
603 names.append(name)
604 value = js[name]
605 # Check no other oneof field is parsed.
606 if field.containing_oneof is not None and value is not None:
607 oneof_name = field.containing_oneof.name
608 if oneof_name in names:
609 raise ParseError(
610 'Message type "{0}" should not have multiple '
611 '"{1}" oneof fields at "{2}".'.format(
612 message.DESCRIPTOR.full_name, oneof_name, path
613 )
614 )
615 names.append(oneof_name)
616
617 if value is None:
618 if (
619 field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE
620 and field.message_type.full_name == 'google.protobuf.Value'
621 ):
622 sub_message = getattr(message, field.name)
623 sub_message.null_value = 0
624 elif (
625 field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM
626 and field.enum_type.full_name == 'google.protobuf.NullValue'
627 ):
628 setattr(message, field.name, 0)
629 else:
630 message.ClearField(field.name)
631 continue
632
633 # Parse field value.
634 if _IsMapEntry(field):
635 message.ClearField(field.name)
636 self._ConvertMapFieldValue(
637 value, message, field, '{0}.{1}'.format(path, name)
638 )
639 elif field.is_repeated:
640 message.ClearField(field.name)
641 if not isinstance(value, _LIST_LIKE):
642 raise ParseError(
643 'repeated field {0} must be in [] which is {1} at {2}'.format(
644 name, value, path
645 )
646 )
647 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
648 # Repeated message field.
649 for index, item in enumerate(value):
650 sub_message = getattr(message, field.name).add()
651 # None is a null_value in Value.
652 if (
653 item is None
654 and sub_message.DESCRIPTOR.full_name
655 != 'google.protobuf.Value'
656 ):
657 raise ParseError(
658 'null is not allowed to be used as an element'
659 ' in a repeated field at {0}.{1}[{2}]'.format(
660 path, name, index
661 )
662 )
663 self.ConvertMessage(
664 item, sub_message, '{0}.{1}[{2}]'.format(path, name, index)
665 )
666 else:
667 # Repeated scalar field.
668 for index, item in enumerate(value):
669 if item is None:
670 raise ParseError(
671 'null is not allowed to be used as an element'
672 ' in a repeated field at {0}.{1}[{2}]'.format(
673 path, name, index
674 )
675 )
676 self._ConvertAndAppendScalar(
677 message, field, item, '{0}.{1}[{2}]'.format(path, name, index)
678 )
679 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
680 if field.is_extension:
681 sub_message = message.Extensions[field]
682 else:
683 sub_message = getattr(message, field.name)
684 sub_message.SetInParent()
685 self.ConvertMessage(value, sub_message, '{0}.{1}'.format(path, name))
686 else:
687 if field.is_extension:
688 self._ConvertAndSetScalarExtension(
689 message, field, value, '{0}.{1}'.format(path, name)
690 )
691 else:
692 self._ConvertAndSetScalar(
693 message, field, value, '{0}.{1}'.format(path, name)
694 )
695 except ParseError as e:
696 if field and field.containing_oneof is None:
697 raise ParseError(
698 'Failed to parse {0} field: {1}.'.format(name, e)
699 ) from e
700 else:
701 raise ParseError(str(e)) from e
702 except ValueError as e:
703 raise ParseError(
704 'Failed to parse {0} field: {1}.'.format(name, e)
705 ) from e
706 except TypeError as e:
707 raise ParseError(
708 'Failed to parse {0} field: {1}.'.format(name, e)
709 ) from e
710
711 def _ConvertAnyMessage(self, value, message, path):
712 """Convert a JSON representation into Any message."""
713 if isinstance(value, dict) and not value:
714 return
715 try:
716 type_url = value['@type']
717 except KeyError as e:
718 raise ParseError(
719 '@type is missing when parsing any message at {0}'.format(path)
720 ) from e
721
722 try:
723 sub_message = _CreateMessageFromTypeUrl(type_url, self.descriptor_pool)
724 except TypeError as e:
725 raise ParseError('{0} at {1}'.format(e, path)) from e
726 message_descriptor = sub_message.DESCRIPTOR
727 full_name = message_descriptor.full_name
728 if _IsWrapperMessage(message_descriptor):
729 self._ConvertWrapperMessage(
730 value['value'], sub_message, '{0}.value'.format(path)
731 )
732 elif full_name in _WKTJSONMETHODS:
733 methodcaller(
734 _WKTJSONMETHODS[full_name][1],
735 value['value'],
736 sub_message,
737 '{0}.value'.format(path),
738 )(self)
739 else:
740 del value['@type']
741 try:
742 self._ConvertFieldValuePair(value, sub_message, path)
743 finally:
744 value['@type'] = type_url
745 # Sets Any message
746 message.value = sub_message.SerializeToString()
747 message.type_url = type_url
748
749 def _ConvertGenericMessage(self, value, message, path):
750 """Convert a JSON representation into message with FromJsonString."""
751 # Duration, Timestamp, FieldMask have a FromJsonString method to do the
752 # conversion. Users can also call the method directly.
753 try:
754 message.FromJsonString(value)
755 except ValueError as e:
756 raise ParseError('{0} at {1}'.format(e, path)) from e
757
758 def _ConvertValueMessage(self, value, message, path):
759 """Convert a JSON representation into Value message."""
760 if isinstance(value, dict):
761 self._ConvertStructMessage(value, message.struct_value, path)
762 elif isinstance(value, _LIST_LIKE):
763 self._ConvertListOrTupleValueMessage(value, message.list_value, path)
764 elif value is None:
765 message.null_value = 0
766 elif isinstance(value, bool):
767 message.bool_value = value
768 elif isinstance(value, str):
769 message.string_value = value
770 elif isinstance(value, _INT_OR_FLOAT):
771 message.number_value = value
772 else:
773 raise ParseError(
774 'Value {0} has unexpected type {1} at {2}'.format(
775 value, type(value), path
776 )
777 )
778
779 def _ConvertListOrTupleValueMessage(self, value, message, path):
780 """Convert a JSON representation into ListValue message."""
781 if not isinstance(value, _LIST_LIKE):
782 raise ParseError(
783 'ListValue must be in [] which is {0} at {1}'.format(value, path)
784 )
785 message.ClearField('values')
786 for index, item in enumerate(value):
787 self._ConvertValueMessage(
788 item, message.values.add(), '{0}[{1}]'.format(path, index)
789 )
790
791 def _ConvertStructMessage(self, value, message, path):
792 """Convert a JSON representation into Struct message."""
793 if not isinstance(value, dict):
794 raise ParseError(
795 'Struct must be in a dict which is {0} at {1}'.format(value, path)
796 )
797 # Clear will mark the struct as modified so it will be created even if
798 # there are no values.
799 message.Clear()
800 for key in value:
801 self._ConvertValueMessage(
802 value[key], message.fields[key], '{0}.{1}'.format(path, key)
803 )
804 return
805
806 def _ConvertWrapperMessage(self, value, message, path):
807 """Convert a JSON representation into Wrapper message."""
808 field = message.DESCRIPTOR.fields_by_name['value']
809 self._ConvertAndSetScalar(
810 message, field, value, path='{0}.value'.format(path)
811 )
812
813 def _ConvertMapFieldValue(self, value, message, field, path):
814 """Convert map field value for a message map field.
815
816 Args:
817 value: A JSON object to convert the map field value.
818 message: A protocol message to record the converted data.
819 field: The descriptor of the map field to be converted.
820 path: parent path to log parse error info.
821
822 Raises:
823 ParseError: In case of convert problems.
824 """
825 if not isinstance(value, dict):
826 raise ParseError(
827 'Map field {0} must be in a dict which is {1} at {2}'.format(
828 field.name, value, path
829 )
830 )
831 key_field = field.message_type.fields_by_name['key']
832 value_field = field.message_type.fields_by_name['value']
833 for key in value:
834 key_value = _ConvertScalarFieldValue(
835 key, key_field, '{0}.key'.format(path), True
836 )
837 if value_field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
838 self.ConvertMessage(
839 value[key],
840 getattr(message, field.name)[key_value],
841 '{0}[{1}]'.format(path, key_value),
842 )
843 else:
844 self._ConvertAndSetScalarToMapKey(
845 message,
846 field,
847 key_value,
848 value[key],
849 path='{0}[{1}]'.format(path, key_value),
850 )
851
852 def _ConvertAndSetScalarExtension(
853 self, message, extension_field, js_value, path
854 ):
855 """Convert scalar from js_value and assign it to message.Extensions[extension_field]."""
856 try:
857 message.Extensions[extension_field] = _ConvertScalarFieldValue(
858 js_value, extension_field, path
859 )
860 except EnumStringValueParseError:
861 if not self.ignore_unknown_fields:
862 raise
863
864 def _ConvertAndSetScalar(self, message, field, js_value, path):
865 """Convert scalar from js_value and assign it to message.field."""
866 try:
867 setattr(
868 message, field.name, _ConvertScalarFieldValue(js_value, field, path)
869 )
870 except EnumStringValueParseError:
871 if not self.ignore_unknown_fields:
872 raise
873
874 def _ConvertAndAppendScalar(self, message, repeated_field, js_value, path):
875 """Convert scalar from js_value and append it to message.repeated_field."""
876 try:
877 getattr(message, repeated_field.name).append(
878 _ConvertScalarFieldValue(js_value, repeated_field, path)
879 )
880 except EnumStringValueParseError:
881 if not self.ignore_unknown_fields:
882 raise
883
884 def _ConvertAndSetScalarToMapKey(
885 self, message, map_field, converted_key, js_value, path
886 ):
887 """Convert scalar from 'js_value' and add it to message.map_field[converted_key]."""
888 try:
889 getattr(message, map_field.name)[converted_key] = (
890 _ConvertScalarFieldValue(
891 js_value,
892 map_field.message_type.fields_by_name['value'],
893 path,
894 )
895 )
896 except EnumStringValueParseError:
897 if not self.ignore_unknown_fields:
898 raise
899
900
901def _ConvertScalarFieldValue(value, field, path, require_str=False):
902 """Convert a single scalar field value.
903
904 Args:
905 value: A scalar value to convert the scalar field value.
906 field: The descriptor of the field to convert.
907 path: parent path to log parse error info.
908 require_str: If True, the field value must be a str.
909
910 Returns:
911 The converted scalar field value
912
913 Raises:
914 ParseError: In case of convert problems.
915 EnumStringValueParseError: In case of unknown enum string value.
916 """
917 try:
918 if field.cpp_type in _INT_TYPES:
919 return _ConvertInteger(value)
920 elif field.cpp_type in _FLOAT_TYPES:
921 return _ConvertFloat(value, field)
922 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL:
923 return _ConvertBool(value, require_str)
924 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING:
925 if field.type == descriptor.FieldDescriptor.TYPE_BYTES:
926 if isinstance(value, str):
927 encoded = value.encode('utf-8')
928 else:
929 encoded = value
930 # Add extra padding '='
931 padded_value = encoded + b'=' * (4 - len(encoded) % 4)
932 return base64.urlsafe_b64decode(padded_value)
933 else:
934 # Checking for unpaired surrogates appears to be unreliable,
935 # depending on the specific Python version, so we check manually.
936 if _UNPAIRED_SURROGATE_PATTERN.search(value):
937 raise ParseError('Unpaired surrogate')
938 return value
939 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM:
940 # Convert an enum value.
941 enum_value = field.enum_type.values_by_name.get(value, None)
942 if enum_value is None:
943 try:
944 number = int(value)
945 enum_value = field.enum_type.values_by_number.get(number, None)
946 except ValueError as e:
947 # Since parsing to integer failed and lookup in values_by_name didn't
948 # find this name, we have an enum string value which is unknown.
949 raise EnumStringValueParseError(
950 'Invalid enum value {0} for enum type {1}'.format(
951 value, field.enum_type.full_name
952 )
953 ) from e
954 if enum_value is None:
955 if field.enum_type.is_closed:
956 raise ParseError(
957 'Invalid enum value {0} for enum type {1}'.format(
958 value, field.enum_type.full_name
959 )
960 )
961 else:
962 return number
963 return enum_value.number
964 except EnumStringValueParseError as e:
965 raise EnumStringValueParseError('{0} at {1}'.format(e, path)) from e
966 except ParseError as e:
967 raise ParseError('{0} at {1}'.format(e, path)) from e
968
969
970def _ConvertInteger(value):
971 """Convert an integer.
972
973 Args:
974 value: A scalar value to convert.
975
976 Returns:
977 The integer value.
978
979 Raises:
980 ParseError: If an integer couldn't be consumed.
981 """
982 if isinstance(value, float) and not value.is_integer():
983 raise ParseError("Couldn't parse integer: {0}".format(value))
984
985 if isinstance(value, str) and value.find(' ') != -1:
986 raise ParseError('Couldn\'t parse integer: "{0}"'.format(value))
987
988 if isinstance(value, bool):
989 raise ParseError(
990 'Bool value {0} is not acceptable for integer field'.format(value)
991 )
992
993 try:
994 return int(value)
995 except ValueError as e:
996 # Attempt to parse as an integer-valued float.
997 try:
998 f = float(value)
999 except ValueError:
1000 # Raise the original exception for the int parse.
1001 raise e # pylint: disable=raise-missing-from
1002 if not f.is_integer():
1003 raise ParseError(
1004 'Couldn\'t parse non-integer string: "{0}"'.format(value)
1005 ) from e
1006 return int(f)
1007
1008
1009def _ConvertFloat(value, field):
1010 """Convert an floating point number."""
1011 if isinstance(value, float):
1012 if math.isnan(value):
1013 raise ParseError('Couldn\'t parse NaN, use quoted "NaN" instead')
1014 if math.isinf(value):
1015 if value > 0:
1016 raise ParseError(
1017 "Couldn't parse Infinity or value too large, "
1018 'use quoted "Infinity" instead'
1019 )
1020 else:
1021 raise ParseError(
1022 "Couldn't parse -Infinity or value too small, "
1023 'use quoted "-Infinity" instead'
1024 )
1025 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_FLOAT:
1026 # pylint: disable=protected-access
1027 if value > type_checkers._FLOAT_MAX:
1028 raise ParseError('Float value too large')
1029 # pylint: disable=protected-access
1030 if value < type_checkers._FLOAT_MIN:
1031 raise ParseError('Float value too small')
1032 if value == 'nan':
1033 raise ParseError('Couldn\'t parse float "nan", use "NaN" instead')
1034 try:
1035 # Assume Python compatible syntax.
1036 return float(value)
1037 except ValueError as e:
1038 # Check alternative spellings.
1039 if value == _NEG_INFINITY:
1040 return float('-inf')
1041 elif value == _INFINITY:
1042 return float('inf')
1043 elif value == _NAN:
1044 return float('nan')
1045 else:
1046 raise ParseError("Couldn't parse float: {0}".format(value)) from e
1047
1048
1049def _ConvertBool(value, require_str):
1050 """Convert a boolean value.
1051
1052 Args:
1053 value: A scalar value to convert.
1054 require_str: If True, value must be a str.
1055
1056 Returns:
1057 The bool parsed.
1058
1059 Raises:
1060 ParseError: If a boolean value couldn't be consumed.
1061 """
1062 if require_str:
1063 if value == 'true':
1064 return True
1065 elif value == 'false':
1066 return False
1067 else:
1068 raise ParseError('Expected "true" or "false", not {0}'.format(value))
1069
1070 if not isinstance(value, bool):
1071 raise ParseError('Expected true or false without quotes')
1072 return value
1073
1074
1075_WKTJSONMETHODS = {
1076 'google.protobuf.Any': ['_AnyMessageToJsonObject', '_ConvertAnyMessage'],
1077 'google.protobuf.Duration': [
1078 '_GenericMessageToJsonObject',
1079 '_ConvertGenericMessage',
1080 ],
1081 'google.protobuf.FieldMask': [
1082 '_GenericMessageToJsonObject',
1083 '_ConvertGenericMessage',
1084 ],
1085 'google.protobuf.ListValue': [
1086 '_ListValueMessageToJsonObject',
1087 '_ConvertListOrTupleValueMessage',
1088 ],
1089 'google.protobuf.Struct': [
1090 '_StructMessageToJsonObject',
1091 '_ConvertStructMessage',
1092 ],
1093 'google.protobuf.Timestamp': [
1094 '_GenericMessageToJsonObject',
1095 '_ConvertGenericMessage',
1096 ],
1097 'google.protobuf.Value': [
1098 '_ValueMessageToJsonObject',
1099 '_ConvertValueMessage',
1100 ],
1101}