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