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