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