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 self.preserving_proto_field_name:
228 name = field.name
229 else:
230 name = field.json_name
231 if _IsMapEntry(field):
232 # Convert a map field.
233 v_field = field.message_type.fields_by_name['value']
234 js_map = {}
235 for key in value:
236 if isinstance(key, bool):
237 if key:
238 recorded_key = 'true'
239 else:
240 recorded_key = 'false'
241 else:
242 recorded_key = str(key)
243 js_map[recorded_key] = self._FieldToJsonObject(v_field, value[key])
244 js[name] = js_map
245 elif field.is_repeated:
246 # Convert a repeated field.
247 js[name] = [self._FieldToJsonObject(field, k) for k in value]
248 elif field.is_extension:
249 name = '[%s]' % field.full_name
250 js[name] = self._FieldToJsonObject(field, 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 self.recursion_depth += 1
531 if self.recursion_depth > self.max_recursion_depth:
532 raise ParseError(
533 'Message too deep. Max recursion depth is {0}'.format(
534 self.max_recursion_depth
535 )
536 )
537 message_descriptor = message.DESCRIPTOR
538 full_name = message_descriptor.full_name
539 if not path:
540 path = message_descriptor.name
541 if _IsWrapperMessage(message_descriptor):
542 self._ConvertWrapperMessage(value, message, path)
543 elif full_name in _WKTJSONMETHODS:
544 methodcaller(_WKTJSONMETHODS[full_name][1], value, message, path)(self)
545 else:
546 self._ConvertFieldValuePair(value, message, path)
547 self.recursion_depth -= 1
548
549 def _ConvertFieldValuePair(self, js, message, path):
550 """Convert field value pairs into regular message.
551
552 Args:
553 js: A JSON object to convert the field value pairs.
554 message: A regular protocol message to record the data.
555 path: parent path to log parse error info.
556
557 Raises:
558 ParseError: In case of problems converting.
559 """
560 names = []
561 message_descriptor = message.DESCRIPTOR
562 fields_by_json_name = dict(
563 (f.json_name, f) for f in message_descriptor.fields
564 )
565 for name in js:
566 try:
567 field = fields_by_json_name.get(name, None)
568 if not field:
569 field = message_descriptor.fields_by_name.get(name, None)
570 if not field and _VALID_EXTENSION_NAME.match(name):
571 if not message_descriptor.is_extendable:
572 raise ParseError(
573 'Message type {0} does not have extensions at {1}'.format(
574 message_descriptor.full_name, path
575 )
576 )
577 identifier = name[1:-1] # strip [] brackets
578 # pylint: disable=protected-access
579 field = message.Extensions._FindExtensionByName(identifier)
580 # pylint: enable=protected-access
581 if not field:
582 # Try looking for extension by the message type name, dropping the
583 # field name following the final . separator in full_name.
584 identifier = '.'.join(identifier.split('.')[:-1])
585 # pylint: disable=protected-access
586 field = message.Extensions._FindExtensionByName(identifier)
587 # pylint: enable=protected-access
588 if not field:
589 if self.ignore_unknown_fields:
590 continue
591 raise ParseError(
592 (
593 'Message type "{0}" has no field named "{1}" at "{2}".\n'
594 ' Available Fields(except extensions): "{3}"'
595 ).format(
596 message_descriptor.full_name,
597 name,
598 path,
599 [f.json_name for f in message_descriptor.fields],
600 )
601 )
602 if name in names:
603 raise ParseError(
604 'Message type "{0}" should not have multiple '
605 '"{1}" fields at "{2}".'.format(
606 message.DESCRIPTOR.full_name, name, path
607 )
608 )
609 names.append(name)
610 value = js[name]
611 # Check no other oneof field is parsed.
612 if field.containing_oneof is not None and value is not None:
613 oneof_name = field.containing_oneof.name
614 if oneof_name in names:
615 raise ParseError(
616 'Message type "{0}" should not have multiple '
617 '"{1}" oneof fields at "{2}".'.format(
618 message.DESCRIPTOR.full_name, oneof_name, path
619 )
620 )
621 names.append(oneof_name)
622
623 if value is None:
624 if (
625 field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE
626 and field.message_type.full_name == 'google.protobuf.Value'
627 ):
628 sub_message = getattr(message, field.name)
629 sub_message.null_value = 0
630 elif (
631 field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM
632 and field.enum_type.full_name == 'google.protobuf.NullValue'
633 ):
634 setattr(message, field.name, 0)
635 else:
636 message.ClearField(field.name)
637 continue
638
639 # Parse field value.
640 if _IsMapEntry(field):
641 message.ClearField(field.name)
642 self._ConvertMapFieldValue(
643 value, message, field, '{0}.{1}'.format(path, name)
644 )
645 elif field.is_repeated:
646 message.ClearField(field.name)
647 if not isinstance(value, _LIST_LIKE):
648 raise ParseError(
649 'repeated field {0} must be in [] which is {1} at {2}'.format(
650 name, value, path
651 )
652 )
653 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
654 # Repeated message field.
655 for index, item in enumerate(value):
656 sub_message = getattr(message, field.name).add()
657 # None is a null_value in Value.
658 if (
659 item is None
660 and sub_message.DESCRIPTOR.full_name
661 != 'google.protobuf.Value'
662 ):
663 raise ParseError(
664 'null is not allowed to be used as an element'
665 ' in a repeated field at {0}.{1}[{2}]'.format(
666 path, name, index
667 )
668 )
669 self.ConvertMessage(
670 item, sub_message, '{0}.{1}[{2}]'.format(path, name, index)
671 )
672 else:
673 # Repeated scalar field.
674 for index, item in enumerate(value):
675 if item is None:
676 raise ParseError(
677 'null is not allowed to be used as an element'
678 ' in a repeated field at {0}.{1}[{2}]'.format(
679 path, name, index
680 )
681 )
682 self._ConvertAndAppendScalar(
683 message, field, item, '{0}.{1}[{2}]'.format(path, name, index)
684 )
685 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
686 if field.is_extension:
687 sub_message = message.Extensions[field]
688 else:
689 sub_message = getattr(message, field.name)
690 sub_message.SetInParent()
691 self.ConvertMessage(value, sub_message, '{0}.{1}'.format(path, name))
692 else:
693 if field.is_extension:
694 self._ConvertAndSetScalarExtension(
695 message, field, value, '{0}.{1}'.format(path, name)
696 )
697 else:
698 self._ConvertAndSetScalar(
699 message, field, value, '{0}.{1}'.format(path, name)
700 )
701 except ParseError as e:
702 if field and field.containing_oneof is None:
703 raise ParseError(
704 'Failed to parse {0} field: {1}.'.format(name, e)
705 ) from e
706 else:
707 raise ParseError(str(e)) from e
708 except ValueError as e:
709 raise ParseError(
710 'Failed to parse {0} field: {1}.'.format(name, e)
711 ) from e
712 except TypeError as e:
713 raise ParseError(
714 'Failed to parse {0} field: {1}.'.format(name, e)
715 ) from e
716
717 def _ConvertAnyMessage(self, value, message, path):
718 """Convert a JSON representation into Any message."""
719 if isinstance(value, dict) and not value:
720 return
721 try:
722 type_url = value['@type']
723 except KeyError as e:
724 raise ParseError(
725 '@type is missing when parsing any message at {0}'.format(path)
726 ) from e
727
728 try:
729 sub_message = _CreateMessageFromTypeUrl(type_url, self.descriptor_pool)
730 except TypeError as e:
731 raise ParseError('{0} at {1}'.format(e, path)) from e
732 message_descriptor = sub_message.DESCRIPTOR
733 full_name = message_descriptor.full_name
734 if _IsWrapperMessage(message_descriptor):
735 self._ConvertWrapperMessage(
736 value['value'], sub_message, '{0}.value'.format(path)
737 )
738 elif full_name in _WKTJSONMETHODS:
739 methodcaller(
740 _WKTJSONMETHODS[full_name][1],
741 value['value'],
742 sub_message,
743 '{0}.value'.format(path),
744 )(self)
745 else:
746 del value['@type']
747 try:
748 self._ConvertFieldValuePair(value, sub_message, path)
749 finally:
750 value['@type'] = type_url
751 # Sets Any message
752 message.value = sub_message.SerializeToString()
753 message.type_url = type_url
754
755 def _ConvertGenericMessage(self, value, message, path):
756 """Convert a JSON representation into message with FromJsonString."""
757 # Duration, Timestamp, FieldMask have a FromJsonString method to do the
758 # conversion. Users can also call the method directly.
759 try:
760 message.FromJsonString(value)
761 except ValueError as e:
762 raise ParseError('{0} at {1}'.format(e, path)) from e
763
764 def _ConvertValueMessage(self, value, message, path):
765 """Convert a JSON representation into Value message."""
766 if isinstance(value, dict):
767 self._ConvertStructMessage(value, message.struct_value, path)
768 elif isinstance(value, _LIST_LIKE):
769 self._ConvertListOrTupleValueMessage(value, message.list_value, path)
770 elif value is None:
771 message.null_value = 0
772 elif isinstance(value, bool):
773 message.bool_value = value
774 elif isinstance(value, str):
775 message.string_value = value
776 elif isinstance(value, _INT_OR_FLOAT):
777 message.number_value = value
778 else:
779 raise ParseError(
780 'Value {0} has unexpected type {1} at {2}'.format(
781 value, type(value), path
782 )
783 )
784
785 def _ConvertListOrTupleValueMessage(self, value, message, path):
786 """Convert a JSON representation into ListValue message."""
787 if not isinstance(value, _LIST_LIKE):
788 raise ParseError(
789 'ListValue must be in [] which is {0} at {1}'.format(value, path)
790 )
791 message.ClearField('values')
792 for index, item in enumerate(value):
793 self._ConvertValueMessage(
794 item, message.values.add(), '{0}[{1}]'.format(path, index)
795 )
796
797 def _ConvertStructMessage(self, value, message, path):
798 """Convert a JSON representation into Struct message."""
799 if not isinstance(value, dict):
800 raise ParseError(
801 'Struct must be in a dict which is {0} at {1}'.format(value, path)
802 )
803 # Clear will mark the struct as modified so it will be created even if
804 # there are no values.
805 message.Clear()
806 for key in value:
807 self._ConvertValueMessage(
808 value[key], message.fields[key], '{0}.{1}'.format(path, key)
809 )
810 return
811
812 def _ConvertWrapperMessage(self, value, message, path):
813 """Convert a JSON representation into Wrapper message."""
814 field = message.DESCRIPTOR.fields_by_name['value']
815 self._ConvertAndSetScalar(
816 message, field, value, path='{0}.value'.format(path)
817 )
818
819 def _ConvertMapFieldValue(self, value, message, field, path):
820 """Convert map field value for a message map field.
821
822 Args:
823 value: A JSON object to convert the map field value.
824 message: A protocol message to record the converted data.
825 field: The descriptor of the map field to be converted.
826 path: parent path to log parse error info.
827
828 Raises:
829 ParseError: In case of convert problems.
830 """
831 if not isinstance(value, dict):
832 raise ParseError(
833 'Map field {0} must be in a dict which is {1} at {2}'.format(
834 field.name, value, path
835 )
836 )
837 key_field = field.message_type.fields_by_name['key']
838 value_field = field.message_type.fields_by_name['value']
839 for key in value:
840 key_value = _ConvertScalarFieldValue(
841 key, key_field, '{0}.key'.format(path), True
842 )
843 if value_field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
844 self.ConvertMessage(
845 value[key],
846 getattr(message, field.name)[key_value],
847 '{0}[{1}]'.format(path, key_value),
848 )
849 else:
850 self._ConvertAndSetScalarToMapKey(
851 message,
852 field,
853 key_value,
854 value[key],
855 path='{0}[{1}]'.format(path, key_value),
856 )
857
858 def _ConvertAndSetScalarExtension(
859 self, message, extension_field, js_value, path
860 ):
861 """Convert scalar from js_value and assign it to message.Extensions[extension_field]."""
862 try:
863 message.Extensions[extension_field] = _ConvertScalarFieldValue(
864 js_value, extension_field, path
865 )
866 except EnumStringValueParseError:
867 if not self.ignore_unknown_fields:
868 raise
869
870 def _ConvertAndSetScalar(self, message, field, js_value, path):
871 """Convert scalar from js_value and assign it to message.field."""
872 try:
873 setattr(
874 message, field.name, _ConvertScalarFieldValue(js_value, field, path)
875 )
876 except EnumStringValueParseError:
877 if not self.ignore_unknown_fields:
878 raise
879
880 def _ConvertAndAppendScalar(self, message, repeated_field, js_value, path):
881 """Convert scalar from js_value and append it to message.repeated_field."""
882 try:
883 getattr(message, repeated_field.name).append(
884 _ConvertScalarFieldValue(js_value, repeated_field, path)
885 )
886 except EnumStringValueParseError:
887 if not self.ignore_unknown_fields:
888 raise
889
890 def _ConvertAndSetScalarToMapKey(
891 self, message, map_field, converted_key, js_value, path
892 ):
893 """Convert scalar from 'js_value' and add it to message.map_field[converted_key]."""
894 try:
895 getattr(message, map_field.name)[converted_key] = (
896 _ConvertScalarFieldValue(
897 js_value,
898 map_field.message_type.fields_by_name['value'],
899 path,
900 )
901 )
902 except EnumStringValueParseError:
903 if not self.ignore_unknown_fields:
904 raise
905
906
907def _ConvertScalarFieldValue(value, field, path, require_str=False):
908 """Convert a single scalar field value.
909
910 Args:
911 value: A scalar value to convert the scalar field value.
912 field: The descriptor of the field to convert.
913 path: parent path to log parse error info.
914 require_str: If True, the field value must be a str.
915
916 Returns:
917 The converted scalar field value
918
919 Raises:
920 ParseError: In case of convert problems.
921 EnumStringValueParseError: In case of unknown enum string value.
922 """
923 try:
924 if field.cpp_type in _INT_TYPES:
925 return _ConvertInteger(value)
926 elif field.cpp_type in _FLOAT_TYPES:
927 return _ConvertFloat(value, field)
928 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL:
929 return _ConvertBool(value, require_str)
930 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING:
931 if field.type == descriptor.FieldDescriptor.TYPE_BYTES:
932 if isinstance(value, str):
933 encoded = value.encode('utf-8')
934 else:
935 encoded = value
936 # Add extra padding '='
937 padded_value = encoded + b'=' * (4 - len(encoded) % 4)
938 return base64.urlsafe_b64decode(padded_value)
939 else:
940 # Checking for unpaired surrogates appears to be unreliable,
941 # depending on the specific Python version, so we check manually.
942 if _UNPAIRED_SURROGATE_PATTERN.search(value):
943 raise ParseError('Unpaired surrogate')
944 return value
945 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM:
946 # Convert an enum value.
947 enum_value = field.enum_type.values_by_name.get(value, None)
948 if enum_value is None:
949 try:
950 number = int(value)
951 enum_value = field.enum_type.values_by_number.get(number, None)
952 except ValueError as e:
953 # Since parsing to integer failed and lookup in values_by_name didn't
954 # find this name, we have an enum string value which is unknown.
955 raise EnumStringValueParseError(
956 'Invalid enum value {0} for enum type {1}'.format(
957 value, field.enum_type.full_name
958 )
959 ) from e
960 if enum_value is None:
961 if field.enum_type.is_closed:
962 raise ParseError(
963 'Invalid enum value {0} for enum type {1}'.format(
964 value, field.enum_type.full_name
965 )
966 )
967 else:
968 return number
969 return enum_value.number
970 except EnumStringValueParseError as e:
971 raise EnumStringValueParseError('{0} at {1}'.format(e, path)) from e
972 except ParseError as e:
973 raise ParseError('{0} at {1}'.format(e, path)) from e
974
975
976def _ConvertInteger(value):
977 """Convert an integer.
978
979 Args:
980 value: A scalar value to convert.
981
982 Returns:
983 The integer value.
984
985 Raises:
986 ParseError: If an integer couldn't be consumed.
987 """
988 if isinstance(value, float) and not value.is_integer():
989 raise ParseError("Couldn't parse integer: {0}".format(value))
990
991 if isinstance(value, str) and value.find(' ') != -1:
992 raise ParseError('Couldn\'t parse integer: "{0}"'.format(value))
993
994 if isinstance(value, bool):
995 raise ParseError(
996 'Bool value {0} is not acceptable for integer field'.format(value)
997 )
998
999 try:
1000 return int(value)
1001 except ValueError as e:
1002 # Attempt to parse as an integer-valued float.
1003 try:
1004 f = float(value)
1005 except ValueError:
1006 # Raise the original exception for the int parse.
1007 raise e # pylint: disable=raise-missing-from
1008 if not f.is_integer():
1009 raise ParseError(
1010 'Couldn\'t parse non-integer string: "{0}"'.format(value)
1011 ) from e
1012 return int(f)
1013
1014
1015def _ConvertFloat(value, field):
1016 """Convert an floating point number."""
1017 if isinstance(value, float):
1018 if math.isnan(value):
1019 raise ParseError('Couldn\'t parse NaN, use quoted "NaN" instead')
1020 if math.isinf(value):
1021 if value > 0:
1022 raise ParseError(
1023 "Couldn't parse Infinity or value too large, "
1024 'use quoted "Infinity" instead'
1025 )
1026 else:
1027 raise ParseError(
1028 "Couldn't parse -Infinity or value too small, "
1029 'use quoted "-Infinity" instead'
1030 )
1031 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_FLOAT:
1032 # pylint: disable=protected-access
1033 if value > type_checkers._FLOAT_MAX:
1034 raise ParseError('Float value too large')
1035 # pylint: disable=protected-access
1036 if value < type_checkers._FLOAT_MIN:
1037 raise ParseError('Float value too small')
1038 if value == 'nan':
1039 raise ParseError('Couldn\'t parse float "nan", use "NaN" instead')
1040 try:
1041 # Assume Python compatible syntax.
1042 return float(value)
1043 except ValueError as e:
1044 # Check alternative spellings.
1045 if value == _NEG_INFINITY:
1046 return float('-inf')
1047 elif value == _INFINITY:
1048 return float('inf')
1049 elif value == _NAN:
1050 return float('nan')
1051 else:
1052 raise ParseError("Couldn't parse float: {0}".format(value)) from e
1053
1054
1055def _ConvertBool(value, require_str):
1056 """Convert a boolean value.
1057
1058 Args:
1059 value: A scalar value to convert.
1060 require_str: If True, value must be a str.
1061
1062 Returns:
1063 The bool parsed.
1064
1065 Raises:
1066 ParseError: If a boolean value couldn't be consumed.
1067 """
1068 if require_str:
1069 if value == 'true':
1070 return True
1071 elif value == 'false':
1072 return False
1073 else:
1074 raise ParseError('Expected "true" or "false", not {0}'.format(value))
1075
1076 if not isinstance(value, bool):
1077 raise ParseError('Expected true or false without quotes')
1078 return value
1079
1080
1081_WKTJSONMETHODS = {
1082 'google.protobuf.Any': ['_AnyMessageToJsonObject', '_ConvertAnyMessage'],
1083 'google.protobuf.Duration': [
1084 '_GenericMessageToJsonObject',
1085 '_ConvertGenericMessage',
1086 ],
1087 'google.protobuf.FieldMask': [
1088 '_GenericMessageToJsonObject',
1089 '_ConvertGenericMessage',
1090 ],
1091 'google.protobuf.ListValue': [
1092 '_ListValueMessageToJsonObject',
1093 '_ConvertListOrTupleValueMessage',
1094 ],
1095 'google.protobuf.Struct': [
1096 '_StructMessageToJsonObject',
1097 '_ConvertStructMessage',
1098 ],
1099 'google.protobuf.Timestamp': [
1100 '_GenericMessageToJsonObject',
1101 '_ConvertGenericMessage',
1102 ],
1103 'google.protobuf.Value': [
1104 '_ValueMessageToJsonObject',
1105 '_ConvertValueMessage',
1106 ],
1107}