Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions python/google/protobuf/internal/json_format_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,26 @@ def testParseEnumValue(self):
'for enum type protobuf_unittest.TestAllTypes.NestedEnum.',
json_format.Parse, '{"optionalNestedEnum": 12345}', message)

def testParseUnknownEnumStringValueProto3(self):
message = json_format_proto3_pb2.TestMessage()
text = '{"enumValue": "UNKNOWN_STRING_VALUE"}'
json_format.Parse(text, message, ignore_unknown_fields=True)
# In proto3, there is no difference between the default value and 0.
self.assertEqual(message.enum_value, 0)

def testParseUnknownEnumStringValueProto2(self):
message = json_format_pb2.TestNumbers()
text = '{"a": "UNKNOWN_STRING_VALUE"}'
json_format.Parse(text, message, ignore_unknown_fields=True)
# In proto2 we can see that the field was not set at all.
self.assertFalse(message.HasField("a"))

def testParseUnknownEnumStringValueRepeatedProto3(self):
message = json_format_proto3_pb2.TestMessage()
text = '{"repeatedEnumValue": ["UNKNOWN_STRING_VALUE", "FOO", "BAR"]}'
json_format.Parse(text, message, ignore_unknown_fields=True)
self.assertEquals(len(message.repeated_enum_value), 2)

def testBytes(self):
message = json_format_proto3_pb2.TestMessage()
# Test url base64
Expand Down
63 changes: 52 additions & 11 deletions python/google/protobuf/json_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,39 @@ class ParseError(Error):
"""Thrown in case of parsing error."""


class _UnknownEnumStringValueParseError(ParseError):
"""Thrown if an unknown enum string value is encountered. This exception never leaks outside of the module."""


class _MaybeSuppressUnknownEnumStringValueParseError():
"""
Example usage:

with _MaybeSuppressUnknownEnumStringValueParseError(True):
...

If should_suppress is True, the _UnknownEnumStringValueParseError will be ignored in the context body.

The motivation for the context manager is to avoid a bigger refactor that would enable _ConvertScalarFieldValue to
signal to the caller that the field should be ignored.

We want to avoid a bigger refactor because we are maintaining a fork and we want changes to be minimal to simplify
merging with upstream.
"""
def __init__(self, should_suppress):
self.should_suppress = should_suppress

def __enter__(self):
pass

def __exit__(self, exc_type, exc_value, traceback):
# The return value from __exit__ indicates if any exception that occurred in the context body should be suppressed.
# We suppress _UnknownEnumStringValueParseError if should_suppress is set.
# See context manager docs:
# https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
return self.should_suppress and exc_type == _UnknownEnumStringValueParseError


def MessageToJson(
message,
including_default_value_fields=False,
Expand Down Expand Up @@ -598,8 +631,9 @@ def _ConvertFieldValuePair(self, js, message):
if item is None:
raise ParseError('null is not allowed to be used as an element'
' in a repeated field.')
getattr(message, field.name).append(
_ConvertScalarFieldValue(item, field))
with _MaybeSuppressUnknownEnumStringValueParseError(self.ignore_unknown_fields):
getattr(message, field.name).append(
_ConvertScalarFieldValue(item, field))
elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
if field.is_extension:
sub_message = message.Extensions[field]
Expand All @@ -608,10 +642,11 @@ def _ConvertFieldValuePair(self, js, message):
sub_message.SetInParent()
self.ConvertMessage(value, sub_message)
else:
if field.is_extension:
message.Extensions[field] = _ConvertScalarFieldValue(value, field)
else:
setattr(message, field.name, _ConvertScalarFieldValue(value, field))
with _MaybeSuppressUnknownEnumStringValueParseError(self.ignore_unknown_fields):
if field.is_extension:
message.Extensions[field] = _ConvertScalarFieldValue(value, field)
else:
setattr(message, field.name, _ConvertScalarFieldValue(value, field))
except ParseError as e:
if field and field.containing_oneof is None:
raise ParseError('Failed to parse {0} field: {1}.'.format(name, e))
Expand Down Expand Up @@ -698,7 +733,8 @@ def _ConvertStructMessage(self, value, message):
def _ConvertWrapperMessage(self, value, message):
"""Convert a JSON representation into Wrapper message."""
field = message.DESCRIPTOR.fields_by_name['value']
setattr(message, 'value', _ConvertScalarFieldValue(value, field))
with _MaybeSuppressUnknownEnumStringValueParseError(self.ignore_unknown_fields):
setattr(message, 'value', _ConvertScalarFieldValue(value, field))

def _ConvertMapFieldValue(self, value, message, field):
"""Convert map field value for a message map field.
Expand All @@ -718,13 +754,15 @@ def _ConvertMapFieldValue(self, value, message, field):
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, True)
with _MaybeSuppressUnknownEnumStringValueParseError(self.ignore_unknown_fields):
key_value = _ConvertScalarFieldValue(key, key_field, True)
if value_field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
self.ConvertMessage(value[key], getattr(
message, field.name)[key_value])
else:
getattr(message, field.name)[key_value] = _ConvertScalarFieldValue(
value[key], value_field)
with _MaybeSuppressUnknownEnumStringValueParseError(self.ignore_unknown_fields):
getattr(message, field.name)[key_value] = _ConvertScalarFieldValue(
value[key], value_field)


def _ConvertScalarFieldValue(value, field, require_str=False):
Expand All @@ -740,6 +778,7 @@ def _ConvertScalarFieldValue(value, field, require_str=False):

Raises:
ParseError: In case of convert problems.
_UnknownEnumStringValueParseError: If unknown enum string value is encountered during parsing.
"""
if field.cpp_type in _INT_TYPES:
return _ConvertInteger(value)
Expand Down Expand Up @@ -770,7 +809,9 @@ def _ConvertScalarFieldValue(value, field, require_str=False):
number = int(value)
enum_value = field.enum_type.values_by_number.get(number, None)
except ValueError:
raise ParseError('Invalid enum value {0} for enum type {1}.'.format(
# The ValueError will be raised by the conversion to int.
# That means that here we know that we have an unknown enum string value.
raise _UnknownEnumStringValueParseError('Invalid enum value {0} for enum type {1}.'.format(
value, field.enum_type.full_name))
if enum_value is None:
if field.file.syntax == 'proto3':
Expand Down