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 field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_FLOAT:
320 if self.float_format:
321 return float(format(value, self.float_format))
322 else:
323 return type_checkers.ToShortestFloat(value)
324
325 return value
326
327 def _AnyMessageToJsonObject(self, message):
328 """Converts Any message according to Proto3 JSON Specification."""
329 if not message.ListFields():
330 return {}
331 # Must print @type first, use OrderedDict instead of {}
332 js = OrderedDict()
333 type_url = message.type_url
334 js['@type'] = type_url
335 sub_message = _CreateMessageFromTypeUrl(type_url, self.descriptor_pool)
336 sub_message.ParseFromString(message.value)
337 message_descriptor = sub_message.DESCRIPTOR
338 full_name = message_descriptor.full_name
339 if _IsWrapperMessage(message_descriptor):
340 js['value'] = self._WrapperMessageToJsonObject(sub_message)
341 return js
342 if full_name in _WKTJSONMETHODS:
343 js['value'] = methodcaller(_WKTJSONMETHODS[full_name][0], sub_message)(
344 self
345 )
346 return js
347 return self._RegularMessageToJsonObject(sub_message, js)
348
349 def _GenericMessageToJsonObject(self, message):
350 """Converts message according to Proto3 JSON Specification."""
351 # Duration, Timestamp and FieldMask have ToJsonString method to do the
352 # convert. Users can also call the method directly.
353 return message.ToJsonString()
354
355 def _ValueMessageToJsonObject(self, message):
356 """Converts Value message according to Proto3 JSON Specification."""
357 which = message.WhichOneof('kind')
358 # If the Value message is not set treat as null_value when serialize
359 # to JSON. The parse back result will be different from original message.
360 if which is None or which == 'null_value':
361 return None
362 if which == 'list_value':
363 return self._ListValueMessageToJsonObject(message.list_value)
364 if which == 'number_value':
365 value = message.number_value
366 if math.isinf(value):
367 raise ValueError(
368 'Fail to serialize Infinity for Value.number_value, '
369 'which would parse as string_value'
370 )
371 if math.isnan(value):
372 raise ValueError(
373 'Fail to serialize NaN for Value.number_value, '
374 'which would parse as string_value'
375 )
376 else:
377 value = getattr(message, which)
378 oneof_descriptor = message.DESCRIPTOR.fields_by_name[which]
379 return self._FieldToJsonObject(oneof_descriptor, value)
380
381 def _ListValueMessageToJsonObject(self, message):
382 """Converts ListValue message according to Proto3 JSON Specification."""
383 return [self._ValueMessageToJsonObject(value) for value in message.values]
384
385 def _StructMessageToJsonObject(self, message):
386 """Converts Struct message according to Proto3 JSON Specification."""
387 fields = message.fields
388 ret = {}
389 for key in fields:
390 ret[key] = self._ValueMessageToJsonObject(fields[key])
391 return ret
392
393 def _WrapperMessageToJsonObject(self, message):
394 return self._FieldToJsonObject(
395 message.DESCRIPTOR.fields_by_name['value'], message.value
396 )
397
398
399def _IsWrapperMessage(message_descriptor):
400 return message_descriptor.file.name == 'google/protobuf/wrappers.proto'
401
402
403def _DuplicateChecker(js):
404 result = {}
405 for name, value in js:
406 if name in result:
407 raise ParseError('Failed to load JSON: duplicate key {0}.'.format(name))
408 result[name] = value
409 return result
410
411
412def _CreateMessageFromTypeUrl(type_url, descriptor_pool):
413 """Creates a message from a type URL."""
414 db = symbol_database.Default()
415 pool = db.pool if descriptor_pool is None else descriptor_pool
416 type_name = type_url.split('/')[-1]
417 try:
418 message_descriptor = pool.FindMessageTypeByName(type_name)
419 except KeyError as e:
420 raise TypeError(
421 'Can not find message descriptor by type_url: {0}'.format(type_url)
422 ) from e
423 message_class = message_factory.GetMessageClass(message_descriptor)
424 return message_class()
425
426
427def Parse(
428 text,
429 message,
430 ignore_unknown_fields=False,
431 descriptor_pool=None,
432 max_recursion_depth=100,
433):
434 """Parses a JSON representation of a protocol message into a message.
435
436 Args:
437 text: Message JSON representation.
438 message: A protocol buffer message to merge into.
439 ignore_unknown_fields: If True, do not raise errors for unknown fields.
440 descriptor_pool: A Descriptor Pool for resolving types. If None use the
441 default.
442 max_recursion_depth: max recursion depth of JSON message to be deserialized.
443 JSON messages over this depth will fail to be deserialized. Default value
444 is 100.
445
446 Returns:
447 The same message passed as argument.
448
449 Raises::
450 ParseError: On JSON parsing problems.
451 """
452 if not isinstance(text, str):
453 text = text.decode('utf-8')
454
455 try:
456 js = json.loads(text, object_pairs_hook=_DuplicateChecker)
457 except Exception as e:
458 raise ParseError('Failed to load JSON: {0}.'.format(str(e))) from e
459
460 try:
461 return ParseDict(
462 js, message, ignore_unknown_fields, descriptor_pool, max_recursion_depth
463 )
464 except ParseError as e:
465 raise e
466 except Exception as e:
467 raise ParseError(
468 'Failed to parse JSON: {0}: {1}.'.format(type(e).__name__, str(e))
469 ) from e
470
471
472def ParseDict(
473 js_dict,
474 message,
475 ignore_unknown_fields=False,
476 descriptor_pool=None,
477 max_recursion_depth=100,
478):
479 """Parses a JSON dictionary representation into a message.
480
481 Args:
482 js_dict: Dict representation of a JSON message.
483 message: A protocol buffer message to merge into.
484 ignore_unknown_fields: If True, do not raise errors for unknown fields.
485 descriptor_pool: A Descriptor Pool for resolving types. If None use the
486 default.
487 max_recursion_depth: max recursion depth of JSON message to be deserialized.
488 JSON messages over this depth will fail to be deserialized. Default value
489 is 100.
490
491 Returns:
492 The same message passed as argument.
493 """
494 parser = _Parser(ignore_unknown_fields, descriptor_pool, max_recursion_depth)
495 parser.ConvertMessage(js_dict, message, '')
496 return message
497
498
499_INT_OR_FLOAT = (int, float)
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):
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 self._ConvertFieldValuePair(value, sub_message, path)
737 value['@type'] = type_url
738 # Sets Any message
739 message.value = sub_message.SerializeToString()
740 message.type_url = type_url
741
742 def _ConvertGenericMessage(self, value, message, path):
743 """Convert a JSON representation into message with FromJsonString."""
744 # Duration, Timestamp, FieldMask have a FromJsonString method to do the
745 # conversion. Users can also call the method directly.
746 try:
747 message.FromJsonString(value)
748 except ValueError as e:
749 raise ParseError('{0} at {1}'.format(e, path)) from e
750
751 def _ConvertValueMessage(self, value, message, path):
752 """Convert a JSON representation into Value message."""
753 if isinstance(value, dict):
754 self._ConvertStructMessage(value, message.struct_value, path)
755 elif isinstance(value, list):
756 self._ConvertListValueMessage(value, message.list_value, path)
757 elif value is None:
758 message.null_value = 0
759 elif isinstance(value, bool):
760 message.bool_value = value
761 elif isinstance(value, str):
762 message.string_value = value
763 elif isinstance(value, _INT_OR_FLOAT):
764 message.number_value = value
765 else:
766 raise ParseError(
767 'Value {0} has unexpected type {1} at {2}'.format(
768 value, type(value), path
769 )
770 )
771
772 def _ConvertListValueMessage(self, value, message, path):
773 """Convert a JSON representation into ListValue message."""
774 if not isinstance(value, list):
775 raise ParseError(
776 'ListValue must be in [] which is {0} at {1}'.format(value, path)
777 )
778 message.ClearField('values')
779 for index, item in enumerate(value):
780 self._ConvertValueMessage(
781 item, message.values.add(), '{0}[{1}]'.format(path, index)
782 )
783
784 def _ConvertStructMessage(self, value, message, path):
785 """Convert a JSON representation into Struct message."""
786 if not isinstance(value, dict):
787 raise ParseError(
788 'Struct must be in a dict which is {0} at {1}'.format(value, path)
789 )
790 # Clear will mark the struct as modified so it will be created even if
791 # there are no values.
792 message.Clear()
793 for key in value:
794 self._ConvertValueMessage(
795 value[key], message.fields[key], '{0}.{1}'.format(path, key)
796 )
797 return
798
799 def _ConvertWrapperMessage(self, value, message, path):
800 """Convert a JSON representation into Wrapper message."""
801 field = message.DESCRIPTOR.fields_by_name['value']
802 self._ConvertAndSetScalar(message, field, value, path='{0}.value'.format(path))
803
804 def _ConvertMapFieldValue(self, value, message, field, path):
805 """Convert map field value for a message map field.
806
807 Args:
808 value: A JSON object to convert the map field value.
809 message: A protocol message to record the converted data.
810 field: The descriptor of the map field to be converted.
811 path: parent path to log parse error info.
812
813 Raises:
814 ParseError: In case of convert problems.
815 """
816 if not isinstance(value, dict):
817 raise ParseError(
818 'Map field {0} must be in a dict which is {1} at {2}'.format(
819 field.name, value, path
820 )
821 )
822 key_field = field.message_type.fields_by_name['key']
823 value_field = field.message_type.fields_by_name['value']
824 for key in value:
825 key_value = _ConvertScalarFieldValue(
826 key, key_field, '{0}.key'.format(path), True
827 )
828 if value_field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
829 self.ConvertMessage(
830 value[key],
831 getattr(message, field.name)[key_value],
832 '{0}[{1}]'.format(path, key_value),
833 )
834 else:
835 self._ConvertAndSetScalarToMapKey(
836 message,
837 field,
838 key_value,
839 value[key],
840 path='{0}[{1}]'.format(path, key_value))
841
842 def _ConvertAndSetScalarExtension(self, message, extension_field, js_value, path):
843 """Convert scalar from js_value and assign it to message.Extensions[extension_field]."""
844 try:
845 message.Extensions[extension_field] = _ConvertScalarFieldValue(
846 js_value, extension_field, path)
847 except EnumStringValueParseError:
848 if not self.ignore_unknown_fields:
849 raise
850
851 def _ConvertAndSetScalar(self, message, field, js_value, path):
852 """Convert scalar from js_value and assign it to message.field."""
853 try:
854 setattr(
855 message,
856 field.name,
857 _ConvertScalarFieldValue(js_value, field, path))
858 except EnumStringValueParseError:
859 if not self.ignore_unknown_fields:
860 raise
861
862 def _ConvertAndAppendScalar(self, message, repeated_field, js_value, path):
863 """Convert scalar from js_value and append it to message.repeated_field."""
864 try:
865 getattr(message, repeated_field.name).append(
866 _ConvertScalarFieldValue(js_value, repeated_field, path))
867 except EnumStringValueParseError:
868 if not self.ignore_unknown_fields:
869 raise
870
871 def _ConvertAndSetScalarToMapKey(self, message, map_field, converted_key, js_value, path):
872 """Convert scalar from 'js_value' and add it to message.map_field[converted_key]."""
873 try:
874 getattr(message, map_field.name)[converted_key] = _ConvertScalarFieldValue(
875 js_value, map_field.message_type.fields_by_name['value'], path,
876 )
877 except EnumStringValueParseError:
878 if not self.ignore_unknown_fields:
879 raise
880
881
882def _ConvertScalarFieldValue(value, field, path, require_str=False):
883 """Convert a single scalar field value.
884
885 Args:
886 value: A scalar value to convert the scalar field value.
887 field: The descriptor of the field to convert.
888 path: parent path to log parse error info.
889 require_str: If True, the field value must be a str.
890
891 Returns:
892 The converted scalar field value
893
894 Raises:
895 ParseError: In case of convert problems.
896 EnumStringValueParseError: In case of unknown enum string value.
897 """
898 try:
899 if field.cpp_type in _INT_TYPES:
900 return _ConvertInteger(value)
901 elif field.cpp_type in _FLOAT_TYPES:
902 return _ConvertFloat(value, field)
903 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL:
904 return _ConvertBool(value, require_str)
905 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING:
906 if field.type == descriptor.FieldDescriptor.TYPE_BYTES:
907 if isinstance(value, str):
908 encoded = value.encode('utf-8')
909 else:
910 encoded = value
911 # Add extra padding '='
912 padded_value = encoded + b'=' * (4 - len(encoded) % 4)
913 return base64.urlsafe_b64decode(padded_value)
914 else:
915 # Checking for unpaired surrogates appears to be unreliable,
916 # depending on the specific Python version, so we check manually.
917 if _UNPAIRED_SURROGATE_PATTERN.search(value):
918 raise ParseError('Unpaired surrogate')
919 return value
920 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM:
921 # Convert an enum value.
922 enum_value = field.enum_type.values_by_name.get(value, None)
923 if enum_value is None:
924 try:
925 number = int(value)
926 enum_value = field.enum_type.values_by_number.get(number, None)
927 except ValueError as e:
928 # Since parsing to integer failed and lookup in values_by_name didn't
929 # find this name, we have an enum string value which is unknown.
930 raise EnumStringValueParseError(
931 'Invalid enum value {0} for enum type {1}'.format(
932 value, field.enum_type.full_name
933 )
934 ) from e
935 if enum_value is None:
936 if field.enum_type.is_closed:
937 raise ParseError(
938 'Invalid enum value {0} for enum type {1}'.format(
939 value, field.enum_type.full_name
940 )
941 )
942 else:
943 return number
944 return enum_value.number
945 except EnumStringValueParseError as e:
946 raise EnumStringValueParseError('{0} at {1}'.format(e, path)) from e
947 except ParseError as e:
948 raise ParseError('{0} at {1}'.format(e, path)) from e
949
950
951def _ConvertInteger(value):
952 """Convert an integer.
953
954 Args:
955 value: A scalar value to convert.
956
957 Returns:
958 The integer value.
959
960 Raises:
961 ParseError: If an integer couldn't be consumed.
962 """
963 if isinstance(value, float) and not value.is_integer():
964 raise ParseError("Couldn't parse integer: {0}".format(value))
965
966 if isinstance(value, str) and value.find(' ') != -1:
967 raise ParseError('Couldn\'t parse integer: "{0}"'.format(value))
968
969 if isinstance(value, bool):
970 raise ParseError(
971 'Bool value {0} is not acceptable for integer field'.format(value)
972 )
973
974 return int(value)
975
976
977def _ConvertFloat(value, field):
978 """Convert an floating point number."""
979 if isinstance(value, float):
980 if math.isnan(value):
981 raise ParseError('Couldn\'t parse NaN, use quoted "NaN" instead')
982 if math.isinf(value):
983 if value > 0:
984 raise ParseError(
985 "Couldn't parse Infinity or value too large, "
986 'use quoted "Infinity" instead'
987 )
988 else:
989 raise ParseError(
990 "Couldn't parse -Infinity or value too small, "
991 'use quoted "-Infinity" instead'
992 )
993 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_FLOAT:
994 # pylint: disable=protected-access
995 if value > type_checkers._FLOAT_MAX:
996 raise ParseError('Float value too large')
997 # pylint: disable=protected-access
998 if value < type_checkers._FLOAT_MIN:
999 raise ParseError('Float value too small')
1000 if value == 'nan':
1001 raise ParseError('Couldn\'t parse float "nan", use "NaN" instead')
1002 try:
1003 # Assume Python compatible syntax.
1004 return float(value)
1005 except ValueError as e:
1006 # Check alternative spellings.
1007 if value == _NEG_INFINITY:
1008 return float('-inf')
1009 elif value == _INFINITY:
1010 return float('inf')
1011 elif value == _NAN:
1012 return float('nan')
1013 else:
1014 raise ParseError("Couldn't parse float: {0}".format(value)) from e
1015
1016
1017def _ConvertBool(value, require_str):
1018 """Convert a boolean value.
1019
1020 Args:
1021 value: A scalar value to convert.
1022 require_str: If True, value must be a str.
1023
1024 Returns:
1025 The bool parsed.
1026
1027 Raises:
1028 ParseError: If a boolean value couldn't be consumed.
1029 """
1030 if require_str:
1031 if value == 'true':
1032 return True
1033 elif value == 'false':
1034 return False
1035 else:
1036 raise ParseError('Expected "true" or "false", not {0}'.format(value))
1037
1038 if not isinstance(value, bool):
1039 raise ParseError('Expected true or false without quotes')
1040 return value
1041
1042
1043_WKTJSONMETHODS = {
1044 'google.protobuf.Any': ['_AnyMessageToJsonObject', '_ConvertAnyMessage'],
1045 'google.protobuf.Duration': [
1046 '_GenericMessageToJsonObject',
1047 '_ConvertGenericMessage',
1048 ],
1049 'google.protobuf.FieldMask': [
1050 '_GenericMessageToJsonObject',
1051 '_ConvertGenericMessage',
1052 ],
1053 'google.protobuf.ListValue': [
1054 '_ListValueMessageToJsonObject',
1055 '_ConvertListValueMessage',
1056 ],
1057 'google.protobuf.Struct': [
1058 '_StructMessageToJsonObject',
1059 '_ConvertStructMessage',
1060 ],
1061 'google.protobuf.Timestamp': [
1062 '_GenericMessageToJsonObject',
1063 '_ConvertGenericMessage',
1064 ],
1065 'google.protobuf.Value': [
1066 '_ValueMessageToJsonObject',
1067 '_ConvertValueMessage',
1068 ],
1069}