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