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