diff --git a/conformance/binary_json_conformance_suite.cc b/conformance/binary_json_conformance_suite.cc index dcf04e988a53f..0398495ba429b 100644 --- a/conformance/binary_json_conformance_suite.cc +++ b/conformance/binary_json_conformance_suite.cc @@ -2525,6 +2525,12 @@ void BinaryAndJsonConformanceSuiteImpl< ExpectParseFailureForJson("Uint64FieldNotInteger", REQUIRED, R"({"optionalUint64": "0.5"})"); + // Parser reject boolean values for integer fields. + ExpectParseFailureForJson("Int32FieldTrueValue", REQUIRED, + R"({"optionalInt32": true})"); + ExpectParseFailureForJson("Int32FieldFalseValue", REQUIRED, + R"({"optionalInt32": false})"); + // Parser reject non-numeric string values. ExpectParseFailureForJson("Int32FieldStringValuePartiallyNumeric", REQUIRED, R"({"optionalInt32": "12abc"})"); @@ -2696,6 +2702,12 @@ void BinaryAndJsonConformanceSuiteImpl< ExpectParseFailureForJson("FloatFieldStringValuePartiallyNumericUnicode", REQUIRED, R"({"optionalFloat": "12谷歌34"})"); + // Parser reject boolean values for float fields. + ExpectParseFailureForJson("FloatFieldTrueValue", REQUIRED, + R"({"optionalFloat": true})"); + ExpectParseFailureForJson("FloatFieldFalseValue", REQUIRED, + R"({"optionalFloat": false})"); + // Double fields. RunValidJsonTest("DoubleFieldMinPositiveValue", REQUIRED, R"({"optionalDouble": 2.22507e-308})", @@ -2761,6 +2773,12 @@ void BinaryAndJsonConformanceSuiteImpl< ExpectParseFailureForJson("DoubleFieldStringValueNonNumeric", REQUIRED, R"({"optionalDouble": "abc"})"); + // Parser reject boolean values for double fields. + ExpectParseFailureForJson("DoubleFieldTrueValue", REQUIRED, + R"({"optionalDouble": true})"); + ExpectParseFailureForJson("DoubleFieldFalseValue", REQUIRED, + R"({"optionalDouble": false})"); + // Enum fields. RunValidJsonTest("EnumField", REQUIRED, R"({"optionalNestedEnum": "FOO"})", "optional_nested_enum: FOO"); @@ -2795,6 +2813,12 @@ void BinaryAndJsonConformanceSuiteImpl< ExpectParseFailureForJson("EnumFieldSingleElementArrayNumericValue", REQUIRED, R"({"optionalNestedEnum": [2]})"); + // Booleans are not allowed for enum fields. + ExpectParseFailureForJson("EnumFieldTrueValue", REQUIRED, + R"({"optionalNestedEnum": true})"); + ExpectParseFailureForJson("EnumFieldFalseValue", REQUIRED, + R"({"optionalNestedEnum": false})"); + if (run_proto3_tests_) { // Unknown enum values are represented as numeric values. RunValidJsonTestWithValidator( diff --git a/conformance/failure_list_python.txt b/conformance/failure_list_python.txt index bd8e7703ba508..ee19466d772c8 100644 --- a/conformance/failure_list_python.txt +++ b/conformance/failure_list_python.txt @@ -1,5 +1,11 @@ Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse, but didn't. Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse, but didn't. +Required.*.JsonInput.DoubleFieldFalseValue # Should have failed to parse, but didn't. +Required.*.JsonInput.DoubleFieldTrueValue # Should have failed to parse, but didn't. +Required.*.JsonInput.EnumFieldFalseValue # Should have failed to parse, but didn't. +Required.*.JsonInput.EnumFieldTrueValue # Should have failed to parse, but didn't. +Required.*.JsonInput.FloatFieldFalseValue # Should have failed to parse, but didn't. +Required.*.JsonInput.FloatFieldTrueValue # Should have failed to parse, but didn't. Required.*.ProtobufInput.BadTag_FieldNumberTooHigh # Should have failed to parse, but didn't. Required.*.ProtobufInput.BadTag_FieldNumberSlightlyTooHigh # Should have failed to parse, but didn't. Required.*.ProtobufInput.BadTag_OverlongVarint # Should have failed to parse, but didn't. diff --git a/conformance/failure_list_python_cpp.txt b/conformance/failure_list_python_cpp.txt index 6e2d335f04bfa..aa42541219896 100644 --- a/conformance/failure_list_python_cpp.txt +++ b/conformance/failure_list_python_cpp.txt @@ -9,6 +9,12 @@ Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse, but didn't. Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse, but didn't. +Required.*.JsonInput.DoubleFieldFalseValue # Should have failed to parse, but didn't. +Required.*.JsonInput.DoubleFieldTrueValue # Should have failed to parse, but didn't. +Required.*.JsonInput.EnumFieldFalseValue # Should have failed to parse, but didn't. +Required.*.JsonInput.EnumFieldTrueValue # Should have failed to parse, but didn't. +Required.*.JsonInput.FloatFieldFalseValue # Should have failed to parse, but didn't. +Required.*.JsonInput.FloatFieldTrueValue # Should have failed to parse, but didn't. Required.*.ProtobufInput.BadTag_FieldNumberSlightlyTooHigh # Should have failed to parse, but didn't. # TODO: Uncomment once conformance tests can express failures that are not expected to be fixed. # Recommended.Editions_Proto2.ProtobufInput.RejectInvalidUtf8.String.MapKey # Should have failed to parse, but didn't. diff --git a/conformance/failure_list_python_upb.txt b/conformance/failure_list_python_upb.txt index c86e178e25b71..96376fc6480a1 100644 --- a/conformance/failure_list_python_upb.txt +++ b/conformance/failure_list_python_upb.txt @@ -1,5 +1,11 @@ Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse, but didn't. Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse, but didn't. +Required.*.JsonInput.DoubleFieldFalseValue # Should have failed to parse, but didn't. +Required.*.JsonInput.DoubleFieldTrueValue # Should have failed to parse, but didn't. +Required.*.JsonInput.EnumFieldFalseValue # Should have failed to parse, but didn't. +Required.*.JsonInput.EnumFieldTrueValue # Should have failed to parse, but didn't. +Required.*.JsonInput.FloatFieldFalseValue # Should have failed to parse, but didn't. +Required.*.JsonInput.FloatFieldTrueValue # Should have failed to parse, but didn't. # TODO: Uncomment once conformance tests can express failures that are not expected to be fixed. # Recommended.Editions_Proto2.ProtobufInput.RejectInvalidUtf8.String.MapKey # Should have failed to parse, but didn't. # Recommended.Editions_Proto2.ProtobufInput.RejectInvalidUtf8.String.MapValue # Should have failed to parse, but didn't. diff --git a/python/google/protobuf/internal/json_format_test.py b/python/google/protobuf/internal/json_format_test.py index 7bc0397fee0dc..f50ca38f00e78 100644 --- a/python/google/protobuf/internal/json_format_test.py +++ b/python/google/protobuf/internal/json_format_test.py @@ -1982,6 +1982,91 @@ def testEnumValueIntOverride(self): {'armor': 1}, ) + def testAliasedEnumValuesSerializeToSharedCustomString(self): + for enum_val in ( + json_enumval_custom_string_pb2.Armor.ARMOR_SABATON, + json_enumval_custom_string_pb2.Armor.ARMOR_SOLLERET, + ): + msg = json_enumval_custom_string_pb2.Knight(armor=enum_val) + json_output = json_format.MessageToJson(msg) + self.assertEqual(json.loads(json_output), {'armor': 'sabaton'}) + + def testNumericCustomStringOptionSerializesAsString(self): + msg = json_enumval_custom_string_pb2.Knight( + armor=json_enumval_custom_string_pb2.Armor.ARMOR_HACHI_MAI_DO + ) + json_output = json_format.MessageToJson(msg) + self.assertEqual(json.loads(json_output), {'armor': '8'}) + + def testParseRawEnumNameWithCustomOptionOk(self): + msg = json_enumval_custom_string_pb2.Knight() + json_format.Parse('{"armor": "ARMOR_GREAT_HELM"}', msg) + self.assertEqual( + msg.armor, json_enumval_custom_string_pb2.Armor.ARMOR_GREAT_HELM + ) + + def testParseAliasedCustomStringOk(self): + msg = json_enumval_custom_string_pb2.Knight() + json_format.Parse('{"armor": "sabaton"}', msg) + self.assertEqual( + msg.armor, json_enumval_custom_string_pb2.Armor.ARMOR_SABATON + ) + + def testParseAliasedRawEnumNameOk(self): + msg = json_enumval_custom_string_pb2.Knight() + json_format.Parse('{"armor": "ARMOR_SOLLERET"}', msg) + self.assertEqual( + msg.armor, json_enumval_custom_string_pb2.Armor.ARMOR_SOLLERET + ) + + def testParseNumericCustomStringOptionOk(self): + msg = json_enumval_custom_string_pb2.Knight() + json_format.Parse('{"armor": "8"}', msg) + self.assertEqual( + msg.armor, json_enumval_custom_string_pb2.Armor.ARMOR_HACHI_MAI_DO + ) + + def testParseIntegerInputForEnumWithNumericCustomOptionOk(self): + msg = json_enumval_custom_string_pb2.Knight() + json_format.Parse('{"armor": 8}', msg) + self.assertEqual( + msg.armor, json_enumval_custom_string_pb2.Armor.ARMOR_HACHI_MAI_DO + ) + + def testParseCustomStringSingleElementArrayFails(self): + msg = json_enumval_custom_string_pb2.Knight() + with self.assertRaises(json_format.ParseError): + json_format.Parse('{"armor": ["gr8 helm"]}', msg) + + def testParseBooleanInputForEnum(self): + # In Python, bool is a subclass of int (int(True) == 1). We document that + # JSON boolean literals like 'true' are accepted for enum fields and coerced + # to integer enum values. + msg = json_enumval_custom_string_pb2.Knight() + json_format.Parse('{"armor": true}', msg) + self.assertEqual( + msg.armor, json_enumval_custom_string_pb2.Armor.ARMOR_GREAT_HELM + ) + + def testParseCustomStringCaseMismatchFails(self): + msg = json_enumval_custom_string_pb2.Knight() + with self.assertRaises(json_format.ParseError): + json_format.Parse('{"armor": "GR8 HELM"}', msg) + + def testParseUnknownEnumStringFails(self): + msg = json_enumval_custom_string_pb2.Knight() + with self.assertRaises(json_format.ParseError): + json_format.Parse('{"armor": "UNKNOWN_ARMOR"}', msg) + + def testParseUnknownEnumStringWithIgnoreUnknownFieldsOk(self): + msg = json_enumval_custom_string_pb2.Knight() + json_format.Parse( + '{"armor": "UNKNOWN_ARMOR"}', msg, ignore_unknown_fields=True + ) + self.assertEqual( + msg.armor, json_enumval_custom_string_pb2.Armor.ARMOR_UNKNOWN + ) + if __name__ == '__main__': unittest.main() diff --git a/python/google/protobuf/internal/message_benchmark.py b/python/google/protobuf/internal/message_benchmark.py deleted file mode 100644 index 6413648028b43..0000000000000 --- a/python/google/protobuf/internal/message_benchmark.py +++ /dev/null @@ -1,300 +0,0 @@ -"""Benchmarks for assigning large NumPy data to protobuf fields.""" - -from collections.abc import Callable -import functools -import sys - -import google_benchmark -import numpy as np - -from google.protobuf import unittest_pb2 - - -@functools.cache -def make_array(num_bytes: int): - return np.zeros(shape=(num_bytes,), dtype=np.uint8) - - -def benchmark( - func: Callable[[google_benchmark.State], None], -) -> Callable[[google_benchmark.State], None]: - """Decorates a function for benchmarking.""" - - @google_benchmark.register - @google_benchmark.option.unit(google_benchmark.kMillisecond) - @google_benchmark.option.arg_names(['num_bytes']) - @google_benchmark.option.arg(1024 * 1024 * 20) - @google_benchmark.option.arg(1024 * 1024 * 100) - @functools.wraps(func) - def wrapper(state: google_benchmark.State) -> None: - func(state) - state.bytes_processed = state.iterations * state.range(0) - - return wrapper - - -@benchmark -def bench_build_message_via_slice(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.int32) - while state: - msg = unittest_pb2.TestAllTypes() - msg.repeated_int32[:] = arr - - -@benchmark -def bench_build_message(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.int32) - while state: - _ = unittest_pb2.TestAllTypes(repeated_int32=arr) - - -@benchmark -def bench_build_message_nested_via_slice(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.int32) - while state: - msg = unittest_pb2.NestedTestAllTypes() - msg.payload.repeated_int32[:] = arr - - -@benchmark -def bench_build_nested_message_dict(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.int32) - while state: - _ = unittest_pb2.NestedTestAllTypes(payload=dict(repeated_int32=arr)) - - -@benchmark -def bench_build_nested_message_int32(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.int32) - while state: - _ = unittest_pb2.NestedTestAllTypes( - payload=unittest_pb2.TestAllTypes(repeated_int32=arr) - ) - - -@benchmark -def bench_build_nested_message_cord(state: google_benchmark.State): - chunk_size = state.range(0) // 1000 - strings = ['a' * chunk_size] * 1000 - while state: - _ = unittest_pb2.NestedTestAllTypes( - payload=unittest_pb2.TestAllTypes(repeated_cord=strings) - ) - - -@benchmark -def bench_build_nested_message_string_piece(state: google_benchmark.State): - chunk_size = state.range(0) // 1000 - strings = ['a' * chunk_size] * 1000 - while state: - _ = unittest_pb2.NestedTestAllTypes( - payload=unittest_pb2.TestAllTypes(repeated_string_piece=strings) - ) - - -@benchmark -def bench_build_nested_message_nested_message(state: google_benchmark.State): - subs = [unittest_pb2.TestAllTypes.NestedMessage(bb=123)] * ( - state.range(0) // 8 - ) - while state: - _ = unittest_pb2.NestedTestAllTypes( - payload=unittest_pb2.TestAllTypes(repeated_nested_message=subs) - ) - - -@benchmark -def bench_assign_repeated_float(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.float32) - msg = unittest_pb2.TestAllTypes() - msg_source = unittest_pb2.TestAllTypes() - msg_source.repeated_float.extend(arr) - while state: - state.pause_timing() - msg.Clear() - state.resume_timing() - msg.repeated_float[:] = msg_source.repeated_float - - -@benchmark -def bench_assign_repeated_int64_to_int32(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.int64) - msg = unittest_pb2.TestAllTypes() - msg_source = unittest_pb2.TestAllTypes() - msg_source.repeated_int64.extend(arr) - while state: - state.pause_timing() - msg.Clear() - state.resume_timing() - msg.repeated_int32[:] = msg_source.repeated_int64 - - -@benchmark -def bench_assign_repeated_double_to_float(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.float64) - msg = unittest_pb2.TestAllTypes() - msg_source = unittest_pb2.TestAllTypes() - msg_source.repeated_double.extend(arr) - while state: - state.pause_timing() - msg.Clear() - state.resume_timing() - msg.repeated_float[:] = msg_source.repeated_double - - -@benchmark -def bench_assign_numpy_int64_to_int32(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.int64) - msg = unittest_pb2.TestAllTypes() - while state: - state.pause_timing() - msg.Clear() - state.resume_timing() - msg.repeated_int32[:] = arr - - -@benchmark -def bench_assign_numpy_double_to_float(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.float64) - msg = unittest_pb2.TestAllTypes() - while state: - state.pause_timing() - msg.Clear() - state.resume_timing() - msg.repeated_float[:] = arr - - -@benchmark -def bench_extend_int32(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.int32) - msg = unittest_pb2.TestAllTypes() - while state: - state.pause_timing() - msg.Clear() - state.resume_timing() - msg.repeated_int32.extend(arr) - - -@benchmark -def bench_decode_from_bytes(state: google_benchmark.State): - arr = make_array(state.range(0)) - msg = unittest_pb2.TestAllTypes() - msg.optional_bytes = memoryview(arr) - msg_bytes = msg.SerializeToString() - while state: - _ = unittest_pb2.TestAllTypes.FromString(msg_bytes) - - -@benchmark -def bench_decode_from_memoryview(state: google_benchmark.State): - arr = make_array(state.range(0)) - msg = unittest_pb2.TestAllTypes() - msg.optional_bytes = memoryview(arr) - msg_bytes = memoryview(msg.SerializeToString()) - while state: - _ = unittest_pb2.TestAllTypes.FromString(msg_bytes) - - -@benchmark -def bench_encode_into_bytes(state: google_benchmark.State): - arr = make_array(state.range(0)) - msg = unittest_pb2.TestAllTypes() - msg.optional_bytes = memoryview(arr) - while state: - _ = msg.SerializeToString() - - -@benchmark -def bench_assign_int32(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.int32) - msg = unittest_pb2.TestAllTypes() - while state: - msg.repeated_int32[:] = arr - state.pause_timing() - msg.Clear() - state.resume_timing() - - -@benchmark -def bench_assign_extend_int32(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.int32) - msg = unittest_pb2.TestAllTypes() - msg.repeated_int32[:] = arr - while state: - msg.repeated_int32[len(arr) :] = arr - state.pause_timing() - msg.repeated_int32[:] = arr - state.resume_timing() - - -@benchmark -def bench_assign_slice_int32(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.int32) - half_arr = arr[: len(arr) // 2] - msg = unittest_pb2.TestAllTypes() - msg.repeated_int32.extend(arr) - while state: - msg.repeated_int32[::2] = half_arr - - -@benchmark -def bench_extend_int64(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.int64) - msg = unittest_pb2.TestAllTypes() - while state: - state.pause_timing() - msg.Clear() - state.resume_timing() - msg.repeated_int64.extend(arr) - - -@benchmark -def bench_extend_float(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.float32) - msg = unittest_pb2.TestAllTypes() - while state: - state.pause_timing() - msg.Clear() - state.resume_timing() - msg.repeated_float.extend(arr) - - -@benchmark -def bench_extend_double(state: google_benchmark.State): - arr = make_array(state.range(0)).view(dtype=np.float64) - msg = unittest_pb2.TestAllTypes() - while state: - state.pause_timing() - msg.Clear() - state.resume_timing() - msg.repeated_double.extend(arr) - - -@benchmark -def bench_assign_bytes(state: google_benchmark.State): - arr = make_array(state.range(0)) - msg = unittest_pb2.TestAllTypes() - arr_bytes = arr.tobytes() - while state: - state.pause_timing() - msg.Clear() - state.resume_timing() - msg.optional_bytes = arr_bytes - - -@benchmark -def bench_assign_bytes_with_conversion(state: google_benchmark.State): - arr = make_array(state.range(0)) - msg = unittest_pb2.TestAllTypes() - while state: - state.pause_timing() - msg.Clear() - state.resume_timing() - msg.optional_bytes = memoryview(arr) - - -if __name__ == '__main__': - if any(arg.startswith('--benchmark_filter') for arg in sys.argv): - google_benchmark.main() - else: - print('No benchmark filter specified. Skipping benchmarks.') diff --git a/python/google/protobuf/json_format.py b/python/google/protobuf/json_format.py index b85be581618c0..c0bd9468dca68 100644 --- a/python/google/protobuf/json_format.py +++ b/python/google/protobuf/json_format.py @@ -513,6 +513,7 @@ def __init__( self.descriptor_pool = descriptor_pool self.max_recursion_depth = max_recursion_depth self.recursion_depth = 0 + self._custom_enum_names_cache = {} def ConvertMessage(self, value, message, path): """Convert a JSON object into a message. @@ -848,7 +849,11 @@ def _ConvertMapFieldValue(self, value, message, field, path): 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, + key_field, + '{0}.key'.format(path), + True, + self._custom_enum_names_cache, ) if value_field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: self.ConvertMessage( @@ -868,7 +873,9 @@ def _ConvertMapFieldValue(self, value, message, field, path): def _ConvertAndSetScalar(self, message, field, js_value, path): """Convert scalar from js_value and assign it to message.field.""" try: - value = _ConvertScalarFieldValue(js_value, field, path) + value = _ConvertScalarFieldValue( + js_value, field, path, False, self._custom_enum_names_cache + ) if field.is_extension: message.Extensions[field] = value else: @@ -884,7 +891,9 @@ def _ConvertAndAppendScalar(self, message, repeated_field, js_value, path): repeated = message.Extensions[repeated_field] else: repeated = getattr(message, repeated_field.name) - value = _ConvertScalarFieldValue(js_value, repeated_field, path) + value = _ConvertScalarFieldValue( + js_value, repeated_field, path, False, self._custom_enum_names_cache + ) repeated.append(value) except EnumStringValueParseError: if not self.ignore_unknown_fields: @@ -900,12 +909,15 @@ def _ConvertAndSetScalarToMapKey( js_value, map_field.message_type.fields_by_name['value'], path, + False, + self._custom_enum_names_cache, ) ) except EnumStringValueParseError: if not self.ignore_unknown_fields: raise + def _GetJsonEnumValueOption(ev): """Helper to get the JsonEnumValueOptions for an enum value. @@ -926,7 +938,48 @@ def _GetJsonEnumValueOption(ev): return ev.GetOptions().Extensions[extension_descriptor] return None -def _ConvertScalarFieldValue(value, field, path, require_str=False): + +def _GetCustomJsonEnumNames(enum_type, custom_enum_names_cache=None): + """Helper to get a mapping from custom JSON name to EnumValueDescriptor. + + Args: + enum_type: The EnumDescriptor. + custom_enum_names_cache: An optional dict to store/lookup the cached map. + + Returns: + A dict mapping custom JSON name strings to EnumValueDescriptors. + """ + if ( + custom_enum_names_cache is not None + and enum_type in custom_enum_names_cache + ): + return custom_enum_names_cache[enum_type] + + custom_names = {} + try: + extension_descriptor = descriptor_pool.Default().FindExtensionByName( + 'pb.enumvalue.json' + ) + except KeyError: + extension_descriptor = None + + if extension_descriptor is not None: + for ev in enum_type.values: + options = ev.GetOptions() + if options.HasExtension(extension_descriptor): + option = options.Extensions[extension_descriptor] + if option.HasField('string'): + custom_names[option.string] = ev + + if custom_enum_names_cache is not None: + custom_enum_names_cache[enum_type] = custom_names + + return custom_names + + +def _ConvertScalarFieldValue( + value, field, path, require_str=False, custom_enum_names_cache=None +): """Convert a single scalar field value. Args: @@ -934,6 +987,7 @@ def _ConvertScalarFieldValue(value, field, path, require_str=False): field: The descriptor of the field to convert. path: parent path to log parse error info. require_str: If True, the field value must be a str. + custom_enum_names_cache: An optional dict to store/lookup custom enum names. Returns: The converted scalar field value @@ -968,12 +1022,11 @@ def _ConvertScalarFieldValue(value, field, path, require_str=False): # Convert an enum value. enum_value = field.enum_type.values_by_name.get(value, None) # First check to see if we have a custom enum string. - if enum_value is None: - for ev in field.enum_type.values: - option = _GetJsonEnumValueOption(ev) - if option is not None and option.string == value: - enum_value = ev - break + if enum_value is None and isinstance(value, str): + custom_names = _GetCustomJsonEnumNames( + field.enum_type, custom_enum_names_cache + ) + enum_value = custom_names.get(value, None) # If not, try parsing it as an integer. if enum_value is None: try: diff --git a/src/google/protobuf/json/json_enumval_custom_string.proto b/src/google/protobuf/json/json_enumval_custom_string.proto index 317d19a80521b..c5201d8e5c3d5 100644 --- a/src/google/protobuf/json/json_enumval_custom_string.proto +++ b/src/google/protobuf/json/json_enumval_custom_string.proto @@ -28,4 +28,5 @@ enum Armor { message Knight { Armor armor = 1; + repeated Armor armors = 2; }