Add new including_default_value_without_presence_fields to Python JSON serializer.

This flag has consistent behavior between proto2 and proto3 optionals (by not including either one), unlike including_default_value_fields which does include proto2 optional but excludes proto3 optionals.

including_default_value_fields is now deprecated and will be removed in an upcoming release.

PiperOrigin-RevId: 603156447
pull/15660/head
Protobuf Team Bot 10 months ago committed by Copybara-Service
parent f9ec860691
commit 25c6d34d4e
  1. 1098
      python/google/protobuf/internal/json_format_test.py
  2. 3
      python/google/protobuf/internal/message_test.py
  3. 39
      python/google/protobuf/internal/test_proto2.proto
  4. 3
      python/google/protobuf/internal/test_proto3_optional.proto
  5. 499
      python/google/protobuf/json_format.py

File diff suppressed because it is too large Load Diff

@ -1777,7 +1777,8 @@ class Proto3Test(unittest.TestCase):
# Test has presence:
for field in test_proto3_optional_pb2.TestProto3Optional.DESCRIPTOR.fields:
self.assertTrue(field.has_presence)
if field.name.startswith('optional_'):
self.assertTrue(field.has_presence)
for field in unittest_pb2.TestAllTypes.DESCRIPTOR.fields:
if field.label == descriptor.FieldDescriptor.LABEL_REPEATED:
self.assertFalse(field.has_presence)

@ -0,0 +1,39 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
syntax = "proto2";
package google.protobuf.python.internal;
message TestProto2 {
message NestedMessage {
// The field name "b" fails to compile in proto1 because it conflicts with
// a local variable named "b" in one of the generated methods. Doh.
// This file needs to compile in proto1 to test backwards-compatibility.
optional int32 bb = 1;
}
enum NestedEnum {
UNSPECIFIED = 0;
FOO = 1;
BAR = 2;
BAZ = 3;
NEG = -1; // Intentionally negative.
}
optional int32 optional_int32 = 1;
optional double optional_double = 12;
optional bool optional_bool = 13;
optional string optional_string = 14;
optional bytes optional_bytes = 15;
optional NestedMessage optional_nested_message = 18;
optional NestedEnum optional_nested_enum = 21;
repeated int32 repeated_int32 = 22;
repeated NestedMessage repeated_nested_message = 23;
}

@ -44,4 +44,7 @@ message TestProto3Optional {
optional NestedMessage optional_nested_message = 18;
optional NestedEnum optional_nested_enum = 21;
repeated int32 repeated_int32 = 22;
repeated NestedMessage repeated_nested_message = 23;
}

@ -27,26 +27,33 @@ import math
from operator import methodcaller
import re
from google.protobuf.internal import type_checkers
from google.protobuf import descriptor
from google.protobuf import message_factory
from google.protobuf import symbol_database
from google.protobuf.internal import type_checkers
_INT_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_INT32,
descriptor.FieldDescriptor.CPPTYPE_UINT32,
descriptor.FieldDescriptor.CPPTYPE_INT64,
descriptor.FieldDescriptor.CPPTYPE_UINT64])
_INT64_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_INT64,
descriptor.FieldDescriptor.CPPTYPE_UINT64])
_FLOAT_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_FLOAT,
descriptor.FieldDescriptor.CPPTYPE_DOUBLE])
_INT_TYPES = frozenset([
descriptor.FieldDescriptor.CPPTYPE_INT32,
descriptor.FieldDescriptor.CPPTYPE_UINT32,
descriptor.FieldDescriptor.CPPTYPE_INT64,
descriptor.FieldDescriptor.CPPTYPE_UINT64,
])
_INT64_TYPES = frozenset([
descriptor.FieldDescriptor.CPPTYPE_INT64,
descriptor.FieldDescriptor.CPPTYPE_UINT64,
])
_FLOAT_TYPES = frozenset([
descriptor.FieldDescriptor.CPPTYPE_FLOAT,
descriptor.FieldDescriptor.CPPTYPE_DOUBLE,
])
_INFINITY = 'Infinity'
_NEG_INFINITY = '-Infinity'
_NAN = 'NaN'
_UNPAIRED_SURROGATE_PATTERN = re.compile(
u'[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]')
'[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]'
)
_VALID_EXTENSION_NAME = re.compile(r'\[[a-zA-Z0-9\._]*\]$')
@ -72,28 +79,36 @@ def MessageToJson(
use_integers_for_enums=False,
descriptor_pool=None,
float_precision=None,
ensure_ascii=True):
ensure_ascii=True,
including_default_value_without_presence_fields=False,
):
"""Converts protobuf message to JSON format.
Args:
message: The protocol buffers message instance to serialize.
including_default_value_fields: If True, singular primitive fields,
repeated fields, and map fields will always be serialized. If
False, only serialize non-empty fields. Singular message fields
and oneof fields are not affected by this option.
preserving_proto_field_name: If True, use the original proto field
names as defined in the .proto file. If False, convert the field
names to lowerCamelCase.
indent: The JSON object will be pretty-printed with this indent level.
An indent level of 0 or negative will only insert newlines. If the
indent level is None, no newlines will be inserted.
including_default_value_fields: (DEPRECATED: use
including_default_value_without_presence_fields to correctly treats proto2
and proto3 optional the same). If True, fields without presence (implicit
presence scalars, repeated fields, and map fields) and Proto2 optional
scalars will always be serialized. Singular message fields, oneof fields
and Proto3 optional scalars are not affected by this option.
including_default_value_without_presence_fields: If True, fields without
presence (implicit presence scalars, repeated fields, and map fields) will
always be serialized. Any field that supports presence is not affected by
this option (including singular message fields and oneof fields).
preserving_proto_field_name: If True, use the original proto field names as
defined in the .proto file. If False, convert the field names to
lowerCamelCase.
indent: The JSON object will be pretty-printed with this indent level. An
indent level of 0 or negative will only insert newlines. If the indent
level is None, no newlines will be inserted.
sort_keys: If True, then the output will be sorted by field names.
use_integers_for_enums: If true, print integers instead of enum names.
descriptor_pool: A Descriptor Pool for resolving types. If None use the
default.
default.
float_precision: If set, use this to specify float field valid digits.
ensure_ascii: If True, strings with non-ASCII characters are escaped.
If False, Unicode strings are returned unchanged.
ensure_ascii: If True, strings with non-ASCII characters are escaped. If
False, Unicode strings are returned unchanged.
Returns:
A string containing the JSON formatted protocol buffer message.
@ -103,33 +118,43 @@ def MessageToJson(
preserving_proto_field_name,
use_integers_for_enums,
descriptor_pool,
float_precision=float_precision)
float_precision,
including_default_value_without_presence_fields
)
return printer.ToJsonString(message, indent, sort_keys, ensure_ascii)
def MessageToDict(
message,
including_default_value_fields=False,
including_default_value_without_presence_fields=False,
preserving_proto_field_name=False,
use_integers_for_enums=False,
descriptor_pool=None,
float_precision=None):
float_precision=None,
):
"""Converts protobuf message to a dictionary.
When the dictionary is encoded to JSON, it conforms to proto3 JSON spec.
Args:
message: The protocol buffers message instance to serialize.
including_default_value_fields: If True, singular primitive fields,
repeated fields, and map fields will always be serialized. If
False, only serialize non-empty fields. Singular message fields
and oneof fields are not affected by this option.
preserving_proto_field_name: If True, use the original proto field
names as defined in the .proto file. If False, convert the field
names to lowerCamelCase.
including_default_value_fields: (DEPRECATED: use
including_default_value_without_presence_fields to correctly treats proto2
and proto3 optional the same). If True, fields without presence (implicit
presence scalars, repeated fields, and map fields) and Proto2 optional
scalars will always be serialized. Singular message fields, oneof fields
and Proto3 optional scalars are not affected by this option.
including_default_value_without_presence_fields: If True, fields without
presence (implicit presence scalars, repeated fields, and map fields) will
always be serialized. Any field that supports presence is not affected by
this option (including singular message fields and oneof fields).
preserving_proto_field_name: If True, use the original proto field names as
defined in the .proto file. If False, convert the field names to
lowerCamelCase.
use_integers_for_enums: If true, print integers instead of enum names.
descriptor_pool: A Descriptor Pool for resolving types. If None use the
default.
default.
float_precision: If set, use this to specify float field valid digits.
Returns:
@ -140,15 +165,19 @@ def MessageToDict(
preserving_proto_field_name,
use_integers_for_enums,
descriptor_pool,
float_precision=float_precision)
float_precision,
including_default_value_without_presence_fields,
)
# pylint: disable=protected-access
return printer._MessageToJsonObject(message)
def _IsMapEntry(field):
return (field.type == descriptor.FieldDescriptor.TYPE_MESSAGE and
field.message_type.has_options and
field.message_type.GetOptions().map_entry)
return (
field.type == descriptor.FieldDescriptor.TYPE_MESSAGE
and field.message_type.has_options
and field.message_type.GetOptions().map_entry
)
class _Printer(object):
@ -160,8 +189,13 @@ class _Printer(object):
preserving_proto_field_name=False,
use_integers_for_enums=False,
descriptor_pool=None,
float_precision=None):
float_precision=None,
including_default_value_without_presence_fields=False,
):
self.including_default_value_fields = including_default_value_fields
self.including_default_value_without_presence_fields = (
including_default_value_without_presence_fields
)
self.preserving_proto_field_name = preserving_proto_field_name
self.use_integers_for_enums = use_integers_for_enums
self.descriptor_pool = descriptor_pool
@ -173,7 +207,8 @@ class _Printer(object):
def ToJsonString(self, message, indent, sort_keys, ensure_ascii):
js = self._MessageToJsonObject(message)
return json.dumps(
js, indent=indent, sort_keys=sort_keys, ensure_ascii=ensure_ascii)
js, indent=indent, sort_keys=sort_keys, ensure_ascii=ensure_ascii
)
def _MessageToJsonObject(self, message):
"""Converts message to an object according to Proto3 JSON Specification."""
@ -208,13 +243,11 @@ class _Printer(object):
recorded_key = 'false'
else:
recorded_key = str(key)
js_map[recorded_key] = self._FieldToJsonObject(
v_field, value[key])
js_map[recorded_key] = self._FieldToJsonObject(v_field, value[key])
js[name] = js_map
elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED:
# Convert a repeated field.
js[name] = [self._FieldToJsonObject(field, k)
for k in value]
js[name] = [self._FieldToJsonObject(field, k) for k in value]
elif field.is_extension:
name = '[%s]' % field.full_name
js[name] = self._FieldToJsonObject(field, value)
@ -222,14 +255,33 @@ class _Printer(object):
js[name] = self._FieldToJsonObject(field, value)
# Serialize default value if including_default_value_fields is True.
if self.including_default_value_fields:
if (
self.including_default_value_fields
or self.including_default_value_without_presence_fields
):
message_descriptor = message.DESCRIPTOR
for field in message_descriptor.fields:
# Singular message fields and oneof fields will not be affected.
if ((field.label != descriptor.FieldDescriptor.LABEL_REPEATED and
field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE) or
field.containing_oneof):
# including_default_value doesn't apply to singular messages or any
# field with a containing oneof (including proto3 optionals which use
# a synthetic oneof).
if self.including_default_value_fields and (
(
field.label != descriptor.FieldDescriptor.LABEL_REPEATED
and field.cpp_type
== descriptor.FieldDescriptor.CPPTYPE_MESSAGE
)
or field.containing_oneof
):
continue
# including_default_value_without_presence_fields doesn't apply to
# any field which supports presence.
if (
self.including_default_value_without_presence_fields
and field.has_presence
):
continue
if self.preserving_proto_field_name:
name = field.name
else:
@ -246,7 +298,8 @@ class _Printer(object):
except ValueError as e:
raise SerializeToJsonError(
'Failed to serialize {0} field: {1}.'.format(field.name, e)) from e
'Failed to serialize {0} field: {1}.'.format(field.name, e)
) from e
return js
@ -264,8 +317,10 @@ class _Printer(object):
return enum_value.name
else:
if field.enum_type.is_closed:
raise SerializeToJsonError('Enum field contains an integer value '
'which can not mapped to an enum value.')
raise SerializeToJsonError(
'Enum field contains an integer value '
'which can not mapped to an enum value.'
)
else:
return value
elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING:
@ -310,8 +365,9 @@ class _Printer(object):
js['value'] = self._WrapperMessageToJsonObject(sub_message)
return js
if full_name in _WKTJSONMETHODS:
js['value'] = methodcaller(_WKTJSONMETHODS[full_name][0],
sub_message)(self)
js['value'] = methodcaller(_WKTJSONMETHODS[full_name][0], sub_message)(
self
)
return js
return self._RegularMessageToJsonObject(sub_message, js)
@ -333,11 +389,15 @@ class _Printer(object):
if which == 'number_value':
value = message.number_value
if math.isinf(value):
raise ValueError('Fail to serialize Infinity for Value.number_value, '
'which would parse as string_value')
raise ValueError(
'Fail to serialize Infinity for Value.number_value, '
'which would parse as string_value'
)
if math.isnan(value):
raise ValueError('Fail to serialize NaN for Value.number_value, '
'which would parse as string_value')
raise ValueError(
'Fail to serialize NaN for Value.number_value, '
'which would parse as string_value'
)
else:
value = getattr(message, which)
oneof_descriptor = message.DESCRIPTOR.fields_by_name[which]
@ -345,8 +405,7 @@ class _Printer(object):
def _ListValueMessageToJsonObject(self, message):
"""Converts ListValue message according to Proto3 JSON Specification."""
return [self._ValueMessageToJsonObject(value)
for value in message.values]
return [self._ValueMessageToJsonObject(value) for value in message.values]
def _StructMessageToJsonObject(self, message):
"""Converts Struct message according to Proto3 JSON Specification."""
@ -358,7 +417,8 @@ class _Printer(object):
def _WrapperMessageToJsonObject(self, message):
return self._FieldToJsonObject(
message.DESCRIPTOR.fields_by_name['value'], message.value)
message.DESCRIPTOR.fields_by_name['value'], message.value
)
def _IsWrapperMessage(message_descriptor):
@ -384,16 +444,18 @@ def _CreateMessageFromTypeUrl(type_url, descriptor_pool):
except KeyError as e:
raise TypeError(
'Can not find message descriptor by type_url: {0}'.format(type_url)
) from e
) from e
message_class = message_factory.GetMessageClass(message_descriptor)
return message_class()
def Parse(text,
message,
ignore_unknown_fields=False,
descriptor_pool=None,
max_recursion_depth=100):
def Parse(
text,
message,
ignore_unknown_fields=False,
descriptor_pool=None,
max_recursion_depth=100,
):
"""Parses a JSON representation of a protocol message into a message.
Args:
@ -402,9 +464,9 @@ def Parse(text,
ignore_unknown_fields: If True, do not raise errors for unknown fields.
descriptor_pool: A Descriptor Pool for resolving types. If None use the
default.
max_recursion_depth: max recursion depth of JSON message to be
deserialized. JSON messages over this depth will fail to be
deserialized. Default value is 100.
max_recursion_depth: max recursion depth of JSON message to be deserialized.
JSON messages over this depth will fail to be deserialized. Default value
is 100.
Returns:
The same message passed as argument.
@ -418,15 +480,18 @@ def Parse(text,
js = json.loads(text, object_pairs_hook=_DuplicateChecker)
except ValueError as e:
raise ParseError('Failed to load JSON: {0}.'.format(str(e))) from e
return ParseDict(js, message, ignore_unknown_fields, descriptor_pool,
max_recursion_depth)
return ParseDict(
js, message, ignore_unknown_fields, descriptor_pool, max_recursion_depth
)
def ParseDict(js_dict,
message,
ignore_unknown_fields=False,
descriptor_pool=None,
max_recursion_depth=100):
def ParseDict(
js_dict,
message,
ignore_unknown_fields=False,
descriptor_pool=None,
max_recursion_depth=100,
):
"""Parses a JSON dictionary representation into a message.
Args:
@ -435,9 +500,9 @@ def ParseDict(js_dict,
ignore_unknown_fields: If True, do not raise errors for unknown fields.
descriptor_pool: A Descriptor Pool for resolving types. If None use the
default.
max_recursion_depth: max recursion depth of JSON message to be
deserialized. JSON messages over this depth will fail to be
deserialized. Default value is 100.
max_recursion_depth: max recursion depth of JSON message to be deserialized.
JSON messages over this depth will fail to be deserialized. Default value
is 100.
Returns:
The same message passed as argument.
@ -453,8 +518,9 @@ _INT_OR_FLOAT = (int, float)
class _Parser(object):
"""JSON format parser for protocol message."""
def __init__(self, ignore_unknown_fields, descriptor_pool,
max_recursion_depth):
def __init__(
self, ignore_unknown_fields, descriptor_pool, max_recursion_depth
):
self.ignore_unknown_fields = ignore_unknown_fields
self.descriptor_pool = descriptor_pool
self.max_recursion_depth = max_recursion_depth
@ -473,8 +539,11 @@ class _Parser(object):
"""
self.recursion_depth += 1
if self.recursion_depth > self.max_recursion_depth:
raise ParseError('Message too deep. Max recursion depth is {0}'.format(
self.max_recursion_depth))
raise ParseError(
'Message too deep. Max recursion depth is {0}'.format(
self.max_recursion_depth
)
)
message_descriptor = message.DESCRIPTOR
full_name = message_descriptor.full_name
if not path:
@ -500,8 +569,9 @@ class _Parser(object):
"""
names = []
message_descriptor = message.DESCRIPTOR
fields_by_json_name = dict((f.json_name, f)
for f in message_descriptor.fields)
fields_by_json_name = dict(
(f.json_name, f) for f in message_descriptor.fields
)
for name in js:
try:
field = fields_by_json_name.get(name, None)
@ -511,7 +581,9 @@ class _Parser(object):
if not message_descriptor.is_extendable:
raise ParseError(
'Message type {0} does not have extensions at {1}'.format(
message_descriptor.full_name, path))
message_descriptor.full_name, path
)
)
identifier = name[1:-1] # strip [] brackets
# pylint: disable=protected-access
field = message.Extensions._FindExtensionByName(identifier)
@ -527,33 +599,48 @@ class _Parser(object):
if self.ignore_unknown_fields:
continue
raise ParseError(
('Message type "{0}" has no field named "{1}" at "{2}".\n'
' Available Fields(except extensions): "{3}"').format(
message_descriptor.full_name, name, path,
[f.json_name for f in message_descriptor.fields]))
(
'Message type "{0}" has no field named "{1}" at "{2}".\n'
' Available Fields(except extensions): "{3}"'
).format(
message_descriptor.full_name,
name,
path,
[f.json_name for f in message_descriptor.fields],
)
)
if name in names:
raise ParseError('Message type "{0}" should not have multiple '
'"{1}" fields at "{2}".'.format(
message.DESCRIPTOR.full_name, name, path))
raise ParseError(
'Message type "{0}" should not have multiple '
'"{1}" fields at "{2}".'.format(
message.DESCRIPTOR.full_name, name, path
)
)
names.append(name)
value = js[name]
# Check no other oneof field is parsed.
if field.containing_oneof is not None and value is not None:
oneof_name = field.containing_oneof.name
if oneof_name in names:
raise ParseError('Message type "{0}" should not have multiple '
'"{1}" oneof fields at "{2}".'.format(
message.DESCRIPTOR.full_name, oneof_name,
path))
raise ParseError(
'Message type "{0}" should not have multiple '
'"{1}" oneof fields at "{2}".'.format(
message.DESCRIPTOR.full_name, oneof_name, path
)
)
names.append(oneof_name)
if value is None:
if (field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE
and field.message_type.full_name == 'google.protobuf.Value'):
if (
field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE
and field.message_type.full_name == 'google.protobuf.Value'
):
sub_message = getattr(message, field.name)
sub_message.null_value = 0
elif (field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM
and field.enum_type.full_name == 'google.protobuf.NullValue'):
elif (
field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM
and field.enum_type.full_name == 'google.protobuf.NullValue'
):
setattr(message, field.name, 0)
else:
message.ClearField(field.name)
@ -562,35 +649,51 @@ class _Parser(object):
# Parse field value.
if _IsMapEntry(field):
message.ClearField(field.name)
self._ConvertMapFieldValue(value, message, field,
'{0}.{1}'.format(path, name))
self._ConvertMapFieldValue(
value, message, field, '{0}.{1}'.format(path, name)
)
elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED:
message.ClearField(field.name)
if not isinstance(value, list):
raise ParseError('repeated field {0} must be in [] which is '
'{1} at {2}'.format(name, value, path))
raise ParseError(
'repeated field {0} must be in [] which is {1} at {2}'.format(
name, value, path
)
)
if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
# Repeated message field.
for index, item in enumerate(value):
sub_message = getattr(message, field.name).add()
# None is a null_value in Value.
if (item is None and
sub_message.DESCRIPTOR.full_name != 'google.protobuf.Value'):
raise ParseError('null is not allowed to be used as an element'
' in a repeated field at {0}.{1}[{2}]'.format(
path, name, index))
self.ConvertMessage(item, sub_message,
'{0}.{1}[{2}]'.format(path, name, index))
if (
item is None
and sub_message.DESCRIPTOR.full_name
!= 'google.protobuf.Value'
):
raise ParseError(
'null is not allowed to be used as an element'
' in a repeated field at {0}.{1}[{2}]'.format(
path, name, index
)
)
self.ConvertMessage(
item, sub_message, '{0}.{1}[{2}]'.format(path, name, index)
)
else:
# Repeated scalar field.
for index, item in enumerate(value):
if item is None:
raise ParseError('null is not allowed to be used as an element'
' in a repeated field at {0}.{1}[{2}]'.format(
path, name, index))
raise ParseError(
'null is not allowed to be used as an element'
' in a repeated field at {0}.{1}[{2}]'.format(
path, name, index
)
)
getattr(message, field.name).append(
_ConvertScalarFieldValue(
item, field, '{0}.{1}[{2}]'.format(path, name, index)))
item, field, '{0}.{1}[{2}]'.format(path, name, index)
)
)
elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
if field.is_extension:
sub_message = message.Extensions[field]
@ -601,26 +704,30 @@ class _Parser(object):
else:
if field.is_extension:
message.Extensions[field] = _ConvertScalarFieldValue(
value, field, '{0}.{1}'.format(path, name))
value, field, '{0}.{1}'.format(path, name)
)
else:
setattr(
message, field.name,
_ConvertScalarFieldValue(value, field,
'{0}.{1}'.format(path, name)))
message,
field.name,
_ConvertScalarFieldValue(
value, field, '{0}.{1}'.format(path, name)
),
)
except ParseError as e:
if field and field.containing_oneof is None:
raise ParseError(
'Failed to parse {0} field: {1}.'.format(name, e)
'Failed to parse {0} field: {1}.'.format(name, e)
) from e
else:
raise ParseError(str(e)) from e
except ValueError as e:
raise ParseError(
'Failed to parse {0} field: {1}.'.format(name, e)
'Failed to parse {0} field: {1}.'.format(name, e)
) from e
except TypeError as e:
raise ParseError(
'Failed to parse {0} field: {1}.'.format(name, e)
'Failed to parse {0} field: {1}.'.format(name, e)
) from e
def _ConvertAnyMessage(self, value, message, path):
@ -631,7 +738,7 @@ class _Parser(object):
type_url = value['@type']
except KeyError as e:
raise ParseError(
'@type is missing when parsing any message at {0}'.format(path)
'@type is missing when parsing any message at {0}'.format(path)
) from e
try:
@ -641,12 +748,16 @@ class _Parser(object):
message_descriptor = sub_message.DESCRIPTOR
full_name = message_descriptor.full_name
if _IsWrapperMessage(message_descriptor):
self._ConvertWrapperMessage(value['value'], sub_message,
'{0}.value'.format(path))
self._ConvertWrapperMessage(
value['value'], sub_message, '{0}.value'.format(path)
)
elif full_name in _WKTJSONMETHODS:
methodcaller(_WKTJSONMETHODS[full_name][1], value['value'], sub_message,
'{0}.value'.format(path))(
self)
methodcaller(
_WKTJSONMETHODS[full_name][1],
value['value'],
sub_message,
'{0}.value'.format(path),
)(self)
else:
del value['@type']
self._ConvertFieldValuePair(value, sub_message, path)
@ -679,38 +790,47 @@ class _Parser(object):
elif isinstance(value, _INT_OR_FLOAT):
message.number_value = value
else:
raise ParseError('Value {0} has unexpected type {1} at {2}'.format(
value, type(value), path))
raise ParseError(
'Value {0} has unexpected type {1} at {2}'.format(
value, type(value), path
)
)
def _ConvertListValueMessage(self, value, message, path):
"""Convert a JSON representation into ListValue message."""
if not isinstance(value, list):
raise ParseError('ListValue must be in [] which is {0} at {1}'.format(
value, path))
raise ParseError(
'ListValue must be in [] which is {0} at {1}'.format(value, path)
)
message.ClearField('values')
for index, item in enumerate(value):
self._ConvertValueMessage(item, message.values.add(),
'{0}[{1}]'.format(path, index))
self._ConvertValueMessage(
item, message.values.add(), '{0}[{1}]'.format(path, index)
)
def _ConvertStructMessage(self, value, message, path):
"""Convert a JSON representation into Struct message."""
if not isinstance(value, dict):
raise ParseError('Struct must be in a dict which is {0} at {1}'.format(
value, path))
raise ParseError(
'Struct must be in a dict which is {0} at {1}'.format(value, path)
)
# Clear will mark the struct as modified so it will be created even if
# there are no values.
message.Clear()
for key in value:
self._ConvertValueMessage(value[key], message.fields[key],
'{0}.{1}'.format(path, key))
self._ConvertValueMessage(
value[key], message.fields[key], '{0}.{1}'.format(path, key)
)
return
def _ConvertWrapperMessage(self, value, message, path):
"""Convert a JSON representation into Wrapper message."""
field = message.DESCRIPTOR.fields_by_name['value']
setattr(
message, 'value',
_ConvertScalarFieldValue(value, field, path='{0}.value'.format(path)))
message,
'value',
_ConvertScalarFieldValue(value, field, path='{0}.value'.format(path)),
)
def _ConvertMapFieldValue(self, value, message, field, path):
"""Convert map field value for a message map field.
@ -727,19 +847,25 @@ class _Parser(object):
if not isinstance(value, dict):
raise ParseError(
'Map field {0} must be in a dict which is {1} at {2}'.format(
field.name, value, path))
field.name, value, path
)
)
key_field = field.message_type.fields_by_name['key']
value_field = field.message_type.fields_by_name['value']
for key in value:
key_value = _ConvertScalarFieldValue(key, key_field,
'{0}.key'.format(path), True)
key_value = _ConvertScalarFieldValue(
key, key_field, '{0}.key'.format(path), True
)
if value_field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
self.ConvertMessage(value[key],
getattr(message, field.name)[key_value],
'{0}[{1}]'.format(path, key_value))
self.ConvertMessage(
value[key],
getattr(message, field.name)[key_value],
'{0}[{1}]'.format(path, key_value),
)
else:
getattr(message, field.name)[key_value] = _ConvertScalarFieldValue(
value[key], value_field, path='{0}[{1}]'.format(path, key_value))
value[key], value_field, path='{0}[{1}]'.format(path, key_value)
)
def _ConvertScalarFieldValue(value, field, path, require_str=False):
@ -787,12 +913,18 @@ def _ConvertScalarFieldValue(value, field, path, require_str=False):
number = int(value)
enum_value = field.enum_type.values_by_number.get(number, None)
except ValueError as e:
raise ParseError('Invalid enum value {0} for enum type {1}'.format(
value, field.enum_type.full_name)) from e
raise ParseError(
'Invalid enum value {0} for enum type {1}'.format(
value, field.enum_type.full_name
)
) from e
if enum_value is None:
if field.enum_type.is_closed:
raise ParseError('Invalid enum value {0} for enum type {1}'.format(
value, field.enum_type.full_name))
raise ParseError(
'Invalid enum value {0} for enum type {1}'.format(
value, field.enum_type.full_name
)
)
else:
return number
return enum_value.number
@ -813,14 +945,15 @@ def _ConvertInteger(value):
ParseError: If an integer couldn't be consumed.
"""
if isinstance(value, float) and not value.is_integer():
raise ParseError('Couldn\'t parse integer: {0}'.format(value))
raise ParseError("Couldn't parse integer: {0}".format(value))
if isinstance(value, str) and value.find(' ') != -1:
raise ParseError('Couldn\'t parse integer: "{0}"'.format(value))
if isinstance(value, bool):
raise ParseError('Bool value {0} is not acceptable for '
'integer field'.format(value))
raise ParseError(
'Bool value {0} is not acceptable for integer field'.format(value)
)
return int(value)
@ -832,11 +965,15 @@ def _ConvertFloat(value, field):
raise ParseError('Couldn\'t parse NaN, use quoted "NaN" instead')
if math.isinf(value):
if value > 0:
raise ParseError('Couldn\'t parse Infinity or value too large, '
'use quoted "Infinity" instead')
raise ParseError(
"Couldn't parse Infinity or value too large, "
'use quoted "Infinity" instead'
)
else:
raise ParseError('Couldn\'t parse -Infinity or value too small, '
'use quoted "-Infinity" instead')
raise ParseError(
"Couldn't parse -Infinity or value too small, "
'use quoted "-Infinity" instead'
)
if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_FLOAT:
# pylint: disable=protected-access
if value > type_checkers._FLOAT_MAX:
@ -858,7 +995,7 @@ def _ConvertFloat(value, field):
elif value == _NAN:
return float('nan')
else:
raise ParseError('Couldn\'t parse float: {0}'.format(value)) from e
raise ParseError("Couldn't parse float: {0}".format(value)) from e
def _ConvertBool(value, require_str):
@ -886,19 +1023,31 @@ def _ConvertBool(value, require_str):
raise ParseError('Expected true or false without quotes')
return value
_WKTJSONMETHODS = {
'google.protobuf.Any': ['_AnyMessageToJsonObject',
'_ConvertAnyMessage'],
'google.protobuf.Duration': ['_GenericMessageToJsonObject',
'_ConvertGenericMessage'],
'google.protobuf.FieldMask': ['_GenericMessageToJsonObject',
'_ConvertGenericMessage'],
'google.protobuf.ListValue': ['_ListValueMessageToJsonObject',
'_ConvertListValueMessage'],
'google.protobuf.Struct': ['_StructMessageToJsonObject',
'_ConvertStructMessage'],
'google.protobuf.Timestamp': ['_GenericMessageToJsonObject',
'_ConvertGenericMessage'],
'google.protobuf.Value': ['_ValueMessageToJsonObject',
'_ConvertValueMessage']
'google.protobuf.Any': ['_AnyMessageToJsonObject', '_ConvertAnyMessage'],
'google.protobuf.Duration': [
'_GenericMessageToJsonObject',
'_ConvertGenericMessage',
],
'google.protobuf.FieldMask': [
'_GenericMessageToJsonObject',
'_ConvertGenericMessage',
],
'google.protobuf.ListValue': [
'_ListValueMessageToJsonObject',
'_ConvertListValueMessage',
],
'google.protobuf.Struct': [
'_StructMessageToJsonObject',
'_ConvertStructMessage',
],
'google.protobuf.Timestamp': [
'_GenericMessageToJsonObject',
'_ConvertGenericMessage',
],
'google.protobuf.Value': [
'_ValueMessageToJsonObject',
'_ConvertValueMessage',
],
}

Loading…
Cancel
Save