mirror of
https://github.com/protocolbuffers/protobuf
synced 2026-08-26 02:23:14 -04:00
This implementation is similar to our Java idea in cl/949670944, basically the idea is that we will have a local map (dictionary) that will be populated the very first time we try to parse into an enum field. The lifetime of this dictionary will be tied to the Parser instance, similar to the Java implementation. BENCHMARKS (baseline stats are recorded at cl/952170250): Python: ``` [BENCHMARK] ParseJsonDefault: med 10.06 us/op | p99 11.22 us/op | mean 10.10 ± 0.20 us/op [BENCHMARK] ParseJsonCustom: med 20.33 us/op | p99 22.23 us/op | mean 20.40 ± 0.34 us/op [BENCHMARK] ParseJsonUnknownIgnored: med 24.21 us/op | p99 26.25 us/op | mean 24.31 ± 0.44 us/op [BENCHMARK] ParseRepeatedJsonDefault: med 1.94 us/item | p99 2.05 us/item | mean 1.95 ± 0.03 us/item [BENCHMARK] ParseRepeatedJsonCustom: med 2.26 us/item | p99 2.45 us/item | mean 2.27 ± 0.03 us/item [BENCHMARK] ParseRepeatedJsonUnknownIgnored: med 5.31 us/item | p99 5.52 us/item | mean 5.32 ± 0.05 us/item ``` Cpp: ``` [BENCHMARK] ParseJsonDefault: med 8.66 us/op | p99 9.21 us/op | mean 8.70 ± 0.13 us/op [BENCHMARK] ParseJsonCustom: med 26.33 us/op | p99 29.71 us/op | mean 26.44 ± 0.69 us/op [BENCHMARK] ParseJsonUnknownIgnored: med 28.46 us/op | p99 31.80 us/op | mean 28.59 ± 0.67 us/op [BENCHMARK] ParseRepeatedJsonDefault: med 1.72 us/item | p99 2.19 us/item | mean 1.74 ± 0.06 us/item [BENCHMARK] ParseRepeatedJsonCustom: med 2.12 us/item | p99 2.38 us/item | mean 2.13 ± 0.04 us/item [BENCHMARK] ParseRepeatedJsonUnknownIgnored: med 4.61 us/item | p99 4.92 us/item | mean 4.62 ± 0.07 us/item ``` UPB: ``` [BENCHMARK] ParseJsonDefault: med 11.22 us/op | p99 12.29 us/op | mean 11.27 ± 0.21 us/op [BENCHMARK] ParseJsonCustom: med 37.54 us/op | p99 39.25 us/op | mean 37.65 ± 0.46 us/op [BENCHMARK] ParseJsonUnknownIgnored: med 41.18 us/op | p99 48.45 us/op | mean 41.30 ± 0.89 us/op [BENCHMARK] ParseRepeatedJsonDefault: med 2.10 us/item | p99 2.19 us/item | mean 2.10 ± 0.02 us/item [BENCHMARK] ParseRepeatedJsonCustom: med 2.58 us/item | p99 2.73 us/item | mean 2.59 ± 0.03 us/item [BENCHMARK] ParseRepeatedJsonUnknownIgnored: med 5.70 us/item | p99 5.90 us/item | mean 5.71 ± 0.04 us/item ``` Seeing these benchmarks, we can see a very noticeable improvement in `ParseRepeatedJsonCustom` and `ParseRepeatedJsonUnknownIgnored` cases -- which correspond to the pathological cases that we are worried about. Singular fields are taking longer to parse in our microbenchmark because the cache that we've added is tied to each Parser instance and must be re-instantiated across every `Parse` call. PiperOrigin-RevId: 970528683
2120 lines
73 KiB
Python
2120 lines
73 KiB
Python
# 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
|
|
|
|
"""Test for google.protobuf.json_format."""
|
|
|
|
__author__ = 'jieluo@google.com (Jie Luo)'
|
|
|
|
import json
|
|
import math
|
|
import struct
|
|
import unittest
|
|
|
|
from absl.testing import parameterized
|
|
from google.protobuf import descriptor_pool
|
|
from google.protobuf import json_format
|
|
from google.protobuf.internal import more_messages_pb2
|
|
from google.protobuf.internal import test_proto2_pb2
|
|
from google.protobuf.internal import test_proto3_optional_pb2
|
|
|
|
from google.protobuf import any_pb2
|
|
from google.protobuf import duration_pb2
|
|
from google.protobuf import field_mask_pb2
|
|
from google.protobuf import struct_pb2
|
|
from google.protobuf import timestamp_pb2
|
|
from google.protobuf import wrappers_pb2
|
|
from google.protobuf import any_test_pb2
|
|
from google.protobuf import unittest_mset_pb2
|
|
from google.protobuf import unittest_pb2
|
|
from google.protobuf.json import json_enumval_custom_string_pb2
|
|
from google.protobuf.util import json_format_pb2
|
|
from google.protobuf.util import json_format_proto3_pb2
|
|
|
|
|
|
class JsonFormatBase(parameterized.TestCase):
|
|
|
|
def FillAllFields(self, message):
|
|
message.int32_value = 20
|
|
message.int64_value = -20
|
|
message.uint32_value = 3120987654
|
|
message.uint64_value = 12345678900
|
|
message.float_value = float('-inf')
|
|
message.double_value = 3.1415
|
|
message.bool_value = True
|
|
message.string_value = 'foo'
|
|
message.bytes_value = b'bar'
|
|
message.message_value.value = 10
|
|
message.enum_value = json_format_proto3_pb2.BAR
|
|
# Repeated
|
|
message.repeated_int32_value.append(0x7FFFFFFF)
|
|
message.repeated_int32_value.append(-2147483648)
|
|
message.repeated_int64_value.append(9007199254740992)
|
|
message.repeated_int64_value.append(-9007199254740992)
|
|
message.repeated_uint32_value.append(0xFFFFFFF)
|
|
message.repeated_uint32_value.append(0x7FFFFFF)
|
|
message.repeated_uint64_value.append(9007199254740992)
|
|
message.repeated_uint64_value.append(9007199254740991)
|
|
message.repeated_float_value.append(0)
|
|
|
|
message.repeated_double_value.append(1e-15)
|
|
message.repeated_double_value.append(float('inf'))
|
|
message.repeated_bool_value.append(True)
|
|
message.repeated_bool_value.append(False)
|
|
message.repeated_string_value.append('Few symbols!#$,;')
|
|
message.repeated_string_value.append('bar')
|
|
message.repeated_bytes_value.append(b'foo')
|
|
message.repeated_bytes_value.append(b'bar')
|
|
message.repeated_message_value.add().value = 10
|
|
message.repeated_message_value.add().value = 11
|
|
message.repeated_enum_value.append(json_format_proto3_pb2.FOO)
|
|
message.repeated_enum_value.append(json_format_proto3_pb2.BAR)
|
|
self.message = message
|
|
|
|
def CheckParseBack(self, message, parsed_message):
|
|
json_format.Parse(json_format.MessageToJson(message), parsed_message)
|
|
self.assertEqual(message, parsed_message)
|
|
|
|
def CheckError(self, text, error_message):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError, error_message, json_format.Parse, text, message
|
|
)
|
|
|
|
|
|
class JsonFormatTest(JsonFormatBase):
|
|
|
|
def testEmptyMessageToJson(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
self.assertEqual(json_format.MessageToJson(message), '{}')
|
|
parsed_message = json_format_proto3_pb2.TestMessage()
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
def testPartialMessageToJson(self):
|
|
message = json_format_proto3_pb2.TestMessage(
|
|
string_value='test', repeated_int32_value=[89, 4]
|
|
)
|
|
self.assertEqual(
|
|
json.loads(json_format.MessageToJson(message)),
|
|
json.loads('{"stringValue": "test", "repeatedInt32Value": [89, 4]}'),
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestMessage()
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
def testAllFieldsToJson(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
text = (
|
|
'{"int32Value": 20, '
|
|
'"int64Value": "-20", '
|
|
'"uint32Value": 3120987654,'
|
|
'"uint64Value": "12345678900",'
|
|
'"floatValue": "-Infinity",'
|
|
'"doubleValue": 3.1415,'
|
|
'"boolValue": true,'
|
|
'"stringValue": "foo",'
|
|
'"bytesValue": "YmFy",'
|
|
'"messageValue": {"value": 10},'
|
|
'"enumValue": "BAR",'
|
|
'"repeatedInt32Value": [2147483647, -2147483648],'
|
|
'"repeatedInt64Value": ["9007199254740992", "-9007199254740992"],'
|
|
'"repeatedUint32Value": [268435455, 134217727],'
|
|
'"repeatedUint64Value": ["9007199254740992", "9007199254740991"],'
|
|
'"repeatedFloatValue": [0],'
|
|
'"repeatedDoubleValue": [1e-15, "Infinity"],'
|
|
'"repeatedBoolValue": [true, false],'
|
|
'"repeatedStringValue": ["Few symbols!#$,;", "bar"],'
|
|
'"repeatedBytesValue": ["Zm9v", "YmFy"],'
|
|
'"repeatedMessageValue": [{"value": 10}, {"value": 11}],'
|
|
'"repeatedEnumValue": ["FOO", "BAR"]'
|
|
'}'
|
|
)
|
|
self.FillAllFields(message)
|
|
self.assertEqual(
|
|
json.loads(json_format.MessageToJson(message)), json.loads(text)
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestMessage()
|
|
json_format.Parse(text, parsed_message)
|
|
self.assertEqual(message, parsed_message)
|
|
|
|
def testUnknownEnumToJsonAndBack(self):
|
|
text = '{\n "enumValue": 999\n}'
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
message.enum_value = 999
|
|
self.assertEqual(json_format.MessageToJson(message), text)
|
|
parsed_message = json_format_proto3_pb2.TestMessage()
|
|
json_format.Parse(text, parsed_message)
|
|
self.assertEqual(message, parsed_message)
|
|
|
|
def testExtensionToJsonAndBack(self):
|
|
message = unittest_mset_pb2.TestMessageSetContainer()
|
|
ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension
|
|
ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension
|
|
message.message_set.Extensions[ext1].i = 23
|
|
message.message_set.Extensions[ext2].str = 'foo'
|
|
message_text = json_format.MessageToJson(message)
|
|
parsed_message = unittest_mset_pb2.TestMessageSetContainer()
|
|
json_format.Parse(message_text, parsed_message)
|
|
self.assertEqual(message, parsed_message)
|
|
|
|
def testExtensionErrors(self):
|
|
self.CheckError(
|
|
'{"[extensionField]": {}}',
|
|
'Message type proto3.TestMessage does not have extensions',
|
|
)
|
|
|
|
def testExtensionToDictAndBack(self):
|
|
message = unittest_mset_pb2.TestMessageSetContainer()
|
|
ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension
|
|
ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension
|
|
message.message_set.Extensions[ext1].i = 23
|
|
message.message_set.Extensions[ext2].str = 'foo'
|
|
message_dict = json_format.MessageToDict(message)
|
|
parsed_message = unittest_mset_pb2.TestMessageSetContainer()
|
|
json_format.ParseDict(message_dict, parsed_message)
|
|
self.assertEqual(message, parsed_message)
|
|
|
|
def testExtensionToDictAndBackWithScalar(self):
|
|
message = unittest_pb2.TestAllExtensions()
|
|
ext1 = unittest_pb2.TestNestedExtension.test
|
|
message.Extensions[ext1] = 'data'
|
|
message_dict = json_format.MessageToDict(message)
|
|
parsed_message = unittest_pb2.TestAllExtensions()
|
|
json_format.ParseDict(message_dict, parsed_message)
|
|
self.assertEqual(message, parsed_message)
|
|
|
|
def testScalarExtensionToDictAndBack(self):
|
|
message = unittest_pb2.TestAllExtensions()
|
|
message.Extensions[unittest_pb2.optional_int32_extension] = 7
|
|
message.Extensions[unittest_pb2.optional_string_extension] = 'hello'
|
|
message_dict = json_format.MessageToDict(message)
|
|
self.assertEqual(
|
|
message_dict,
|
|
{
|
|
'[proto2_unittest.optional_int32_extension]': 7,
|
|
'[proto2_unittest.optional_string_extension]': 'hello',
|
|
},
|
|
)
|
|
parsed_message = unittest_pb2.TestAllExtensions()
|
|
json_format.ParseDict(message_dict, parsed_message)
|
|
self.assertEqual(message, parsed_message)
|
|
|
|
def testRepeatedScalarExtensionToDictAndBack(self):
|
|
message = unittest_pb2.TestAllExtensions()
|
|
ext1 = unittest_pb2.repeated_int32_extension
|
|
message.Extensions[ext1].extend([1, 2, 3])
|
|
message_dict = json_format.MessageToDict(message)
|
|
self.assertIn('[proto2_unittest.repeated_int32_extension]', message_dict)
|
|
parsed_message = unittest_pb2.TestAllExtensions()
|
|
json_format.ParseDict(message_dict, parsed_message)
|
|
self.assertEqual(message, parsed_message)
|
|
|
|
def testRepeatedMessageExtensionToDictAndBack(self):
|
|
message = unittest_pb2.TestAllExtensions()
|
|
ext1 = unittest_pb2.repeated_nested_message_extension
|
|
sub = unittest_pb2.TestAllTypes.NestedMessage()
|
|
sub.bb = 1
|
|
message.Extensions[ext1].append(sub)
|
|
message_dict = json_format.MessageToDict(message)
|
|
self.assertIn(
|
|
'[proto2_unittest.repeated_nested_message_extension]', message_dict
|
|
)
|
|
parsed_message = unittest_pb2.TestAllExtensions()
|
|
json_format.ParseDict(message_dict, parsed_message)
|
|
self.assertEqual(message, parsed_message)
|
|
|
|
def testJsonParseDictToAnyDoesNotAlterInput(self):
|
|
orig_dict = {
|
|
'int32Value': 20,
|
|
'@type': 'type.googleapis.com/proto3.TestMessage',
|
|
}
|
|
copied_dict = json.loads(json.dumps(orig_dict))
|
|
parsed_message = any_pb2.Any()
|
|
json_format.ParseDict(copied_dict, parsed_message)
|
|
self.assertEqual(copied_dict, orig_dict)
|
|
|
|
def testExtensionSerializationDictMatchesProto3Spec(self):
|
|
"""See go/proto3-json-spec for spec."""
|
|
message = unittest_mset_pb2.TestMessageSetContainer()
|
|
ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension
|
|
ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension
|
|
message.message_set.Extensions[ext1].i = 23
|
|
message.message_set.Extensions[ext2].str = 'foo'
|
|
message_dict = json_format.MessageToDict(message)
|
|
golden_dict = {
|
|
'messageSet': {
|
|
'[proto2_unittest.TestMessageSetExtension1.message_set_extension]': {
|
|
'i': 23,
|
|
},
|
|
'[proto2_unittest.TestMessageSetExtension2.message_set_extension]': {
|
|
'str': 'foo',
|
|
},
|
|
},
|
|
}
|
|
self.assertEqual(golden_dict, message_dict)
|
|
parsed_msg = unittest_mset_pb2.TestMessageSetContainer()
|
|
json_format.ParseDict(golden_dict, parsed_msg)
|
|
self.assertEqual(message, parsed_msg)
|
|
|
|
def testExtensionSerializationDictMatchesProto3SpecMore(self):
|
|
"""See go/proto3-json-spec for spec."""
|
|
message = json_format_pb2.TestMessageWithExtension()
|
|
ext = json_format_pb2.TestExtension.ext
|
|
message.Extensions[ext].value = 'stuff'
|
|
message_dict = json_format.MessageToDict(message)
|
|
expected_dict = {
|
|
'[proto2_unittest.TestExtension.ext]': {
|
|
'value': 'stuff',
|
|
},
|
|
}
|
|
self.assertEqual(expected_dict, message_dict)
|
|
|
|
def testExtensionSerializationJsonMatchesProto3Spec(self):
|
|
"""See go/proto3-json-spec for spec."""
|
|
message = unittest_mset_pb2.TestMessageSetContainer()
|
|
ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension
|
|
ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension
|
|
message.message_set.Extensions[ext1].i = 23
|
|
message.message_set.Extensions[ext2].str = 'foo'
|
|
message_text = json_format.MessageToJson(message)
|
|
ext1_text = 'proto2_unittest.TestMessageSetExtension1.message_set_extension'
|
|
ext2_text = 'proto2_unittest.TestMessageSetExtension2.message_set_extension'
|
|
golden_text = (
|
|
'{"messageSet": {'
|
|
' "[%s]": {'
|
|
' "i": 23'
|
|
' },'
|
|
' "[%s]": {'
|
|
' "str": "foo"'
|
|
' }'
|
|
'}}'
|
|
) % (ext1_text, ext2_text)
|
|
self.assertEqual(json.loads(golden_text), json.loads(message_text))
|
|
|
|
def testJsonEscapeString(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
message.string_value = '&\n<"\r>\b\t\f\\\001/'
|
|
message.string_value += (b'\xe2\x80\xa8\xe2\x80\xa9').decode('utf-8')
|
|
self.assertEqual(
|
|
json_format.MessageToJson(message),
|
|
'{\n "stringValue": '
|
|
'"&\\n<\\"\\r>\\b\\t\\f\\\\\\u0001/\\u2028\\u2029"\n}',
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestMessage()
|
|
self.CheckParseBack(message, parsed_message)
|
|
text = '{"int32Value": "\u0031"}'
|
|
json_format.Parse(text, message)
|
|
self.assertEqual(message.int32_value, 1)
|
|
|
|
def testProto3Optional_IncludingDefaultValueWithoutPresenceFields(self):
|
|
message = test_proto3_optional_pb2.TestProto3Optional()
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
)
|
|
),
|
|
json.loads('{"repeatedInt32": [], "repeatedNestedMessage": []}'),
|
|
)
|
|
message.optional_int32 = 0
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
)
|
|
),
|
|
json.loads(
|
|
'{"optionalInt32": 0,"repeatedInt32": [],'
|
|
' "repeatedNestedMessage": []}'
|
|
),
|
|
)
|
|
|
|
def testProto2_IncludingDefaultValueWithoutPresenceFields(self):
|
|
message = test_proto2_pb2.TestProto2()
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
)
|
|
),
|
|
json.loads('{"repeatedInt32": [], "repeatedNestedMessage": []}'),
|
|
)
|
|
message.optional_int32 = 0
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
)
|
|
),
|
|
json.loads(
|
|
'{"optionalInt32": 0,"repeatedInt32": [],'
|
|
' "repeatedNestedMessage": []}'
|
|
),
|
|
)
|
|
|
|
def testIntegersRepresentedAsFloat(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
json_format.Parse('{"int32Value": -2.147483648e9}', message)
|
|
self.assertEqual(message.int32_value, -2147483648)
|
|
json_format.Parse('{"int32Value": 1e5}', message)
|
|
self.assertEqual(message.int32_value, 100000)
|
|
json_format.Parse('{"int32Value": 1.0}', message)
|
|
self.assertEqual(message.int32_value, 1)
|
|
|
|
def testIntegersRepresentedAsFloatStrings(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
json_format.Parse('{"int32Value": "-2.147483648e9"}', message)
|
|
self.assertEqual(message.int32_value, -2147483648)
|
|
json_format.Parse('{"int32Value": "1e5"}', message)
|
|
self.assertEqual(message.int32_value, 100000)
|
|
json_format.Parse('{"int32Value": "1.0"}', message)
|
|
self.assertEqual(message.int32_value, 1)
|
|
|
|
def testMapFields(self):
|
|
message = json_format_proto3_pb2.TestNestedMap()
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
)
|
|
),
|
|
json.loads(
|
|
'{'
|
|
'"boolMap": {},'
|
|
'"int32Map": {},'
|
|
'"int64Map": {},'
|
|
'"uint32Map": {},'
|
|
'"uint64Map": {},'
|
|
'"stringMap": {},'
|
|
'"mapMap": {}'
|
|
'}'
|
|
),
|
|
)
|
|
message.bool_map[True] = 1
|
|
message.bool_map[False] = 2
|
|
message.int32_map[1] = 2
|
|
message.int32_map[2] = 3
|
|
message.int64_map[1] = 2
|
|
message.int64_map[2] = 3
|
|
message.uint32_map[1] = 2
|
|
message.uint32_map[2] = 3
|
|
message.uint64_map[1] = 2
|
|
message.uint64_map[2] = 3
|
|
message.string_map['1'] = 2
|
|
message.string_map['null'] = 3
|
|
message.map_map['1'].bool_map[True] = 3
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=False
|
|
)
|
|
),
|
|
json.loads(
|
|
'{'
|
|
'"boolMap": {"false": 2, "true": 1},'
|
|
'"int32Map": {"1": 2, "2": 3},'
|
|
'"int64Map": {"1": 2, "2": 3},'
|
|
'"uint32Map": {"1": 2, "2": 3},'
|
|
'"uint64Map": {"1": 2, "2": 3},'
|
|
'"stringMap": {"1": 2, "null": 3},'
|
|
'"mapMap": {"1": {"boolMap": {"true": 3}}}'
|
|
'}'
|
|
),
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestNestedMap()
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
def testOneofFields(self):
|
|
message = json_format_proto3_pb2.TestOneof()
|
|
# Always print does not affect oneof fields.
|
|
self.assertEqual(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
),
|
|
'{}',
|
|
)
|
|
message.oneof_int32_value = 0
|
|
self.assertEqual(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
),
|
|
'{\n "oneofInt32Value": 0\n}',
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestOneof()
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
def testSurrogates(self):
|
|
# Test correct surrogate handling.
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
json_format.Parse('{"stringValue": "\\uD83D\\uDE01"}', message)
|
|
self.assertEqual(
|
|
message.string_value, b'\xF0\x9F\x98\x81'.decode('utf-8', 'strict')
|
|
)
|
|
|
|
# Error case: unpaired high surrogate.
|
|
self.CheckError(
|
|
'{"stringValue": "\\uD83D"}',
|
|
r'Invalid \\uXXXX escape|Unpaired.*surrogate',
|
|
)
|
|
|
|
# Unpaired low surrogate.
|
|
self.CheckError(
|
|
'{"stringValue": "\\uDE01"}',
|
|
r'Invalid \\uXXXX escape|Unpaired.*surrogate',
|
|
)
|
|
|
|
def testTimestampMessage(self):
|
|
message = json_format_proto3_pb2.TestTimestamp()
|
|
message.value.seconds = 0
|
|
message.value.nanos = 0
|
|
message.repeated_value.add().seconds = 20
|
|
message.repeated_value[0].nanos = 1
|
|
message.repeated_value.add().seconds = 0
|
|
message.repeated_value[1].nanos = 10000
|
|
message.repeated_value.add().seconds = 100000000
|
|
message.repeated_value[2].nanos = 0
|
|
# Maximum time
|
|
message.repeated_value.add().seconds = 253402300799
|
|
message.repeated_value[3].nanos = 999999999
|
|
# Minimum time
|
|
message.repeated_value.add().seconds = -62135596800
|
|
message.repeated_value[4].nanos = 0
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
)
|
|
),
|
|
json.loads(
|
|
'{'
|
|
'"value": "1970-01-01T00:00:00Z",'
|
|
'"repeatedValue": ['
|
|
' "1970-01-01T00:00:20.000000001Z",'
|
|
' "1970-01-01T00:00:00.000010Z",'
|
|
' "1973-03-03T09:46:40Z",'
|
|
' "9999-12-31T23:59:59.999999999Z",'
|
|
' "0001-01-01T00:00:00Z"'
|
|
']'
|
|
'}'
|
|
),
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestTimestamp()
|
|
self.CheckParseBack(message, parsed_message)
|
|
text = (
|
|
r'{"value": "1970-01-01T00:00:00.01+08:00",'
|
|
r'"repeatedValue":['
|
|
r' "1970-01-01T00:00:00.01+08:30",'
|
|
r' "1970-01-01T00:00:00.01-01:23"]}'
|
|
)
|
|
json_format.Parse(text, parsed_message)
|
|
self.assertEqual(parsed_message.value.seconds, -8 * 3600)
|
|
self.assertEqual(parsed_message.value.nanos, 10000000)
|
|
self.assertEqual(parsed_message.repeated_value[0].seconds, -8.5 * 3600)
|
|
self.assertEqual(parsed_message.repeated_value[1].seconds, 3600 + 23 * 60)
|
|
|
|
def testDurationMessage(self):
|
|
message = json_format_proto3_pb2.TestDuration()
|
|
message.value.seconds = 1
|
|
message.repeated_value.add().seconds = 0
|
|
message.repeated_value[0].nanos = 10
|
|
message.repeated_value.add().seconds = -1
|
|
message.repeated_value[1].nanos = -1000
|
|
message.repeated_value.add().seconds = 10
|
|
message.repeated_value[2].nanos = 11000000
|
|
message.repeated_value.add().seconds = -315576000000
|
|
message.repeated_value.add().seconds = 315576000000
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
)
|
|
),
|
|
json.loads(
|
|
'{'
|
|
'"value": "1s",'
|
|
'"repeatedValue": ['
|
|
' "0.000000010s",'
|
|
' "-1.000001s",'
|
|
' "10.011s",'
|
|
' "-315576000000s",'
|
|
' "315576000000s"'
|
|
']'
|
|
'}'
|
|
),
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestDuration()
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
def testFieldMaskMessage(self):
|
|
message = json_format_proto3_pb2.TestFieldMask()
|
|
message.value.paths.append('foo.bar')
|
|
message.value.paths.append('bar')
|
|
self.assertEqual(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
),
|
|
'{\n "value": "foo.bar,bar"\n}',
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestFieldMask()
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
message.value.Clear()
|
|
self.assertEqual(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
),
|
|
'{\n "value": ""\n}',
|
|
)
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
def testWrapperMessage(self):
|
|
message = json_format_proto3_pb2.TestWrapper()
|
|
message.bool_value.value = False
|
|
message.int32_value.value = 0
|
|
message.string_value.value = ''
|
|
message.bytes_value.value = b''
|
|
message.repeated_bool_value.add().value = True
|
|
message.repeated_bool_value.add().value = False
|
|
message.repeated_int32_value.add()
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
)
|
|
),
|
|
json.loads(
|
|
'{\n'
|
|
' "int32Value": 0,'
|
|
' "boolValue": false,'
|
|
' "stringValue": "",'
|
|
' "bytesValue": "",'
|
|
' "repeatedBoolValue": [true, false],'
|
|
' "repeatedInt32Value": [0],'
|
|
' "repeatedUint32Value": [],'
|
|
' "repeatedFloatValue": [],'
|
|
' "repeatedDoubleValue": [],'
|
|
' "repeatedBytesValue": [],'
|
|
' "repeatedInt64Value": [],'
|
|
' "repeatedUint64Value": [],'
|
|
' "repeatedStringValue": []'
|
|
'}'
|
|
),
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestWrapper()
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
def testStructMessage(self):
|
|
message = json_format_proto3_pb2.TestStruct()
|
|
message.value['name'] = 'Jim'
|
|
message.value['age'] = 10
|
|
message.value['attend'] = True
|
|
message.value['email'] = None
|
|
message.value.get_or_create_struct('address')['city'] = 'SFO'
|
|
message.value['address']['house_number'] = 1024
|
|
message.value.get_or_create_struct('empty_struct')
|
|
message.value.get_or_create_list('empty_list')
|
|
struct_list = message.value.get_or_create_list('list')
|
|
struct_list.extend([6, 'seven', True, False, None])
|
|
struct_list.add_struct()['subkey2'] = 9
|
|
message.repeated_value.add()['age'] = 11
|
|
message.repeated_value.add()
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=False
|
|
)
|
|
),
|
|
json.loads(
|
|
'{'
|
|
' "value": {'
|
|
' "address": {'
|
|
' "city": "SFO", '
|
|
' "house_number": 1024'
|
|
' }, '
|
|
' "empty_struct": {}, '
|
|
' "empty_list": [], '
|
|
' "age": 10, '
|
|
' "name": "Jim", '
|
|
' "attend": true, '
|
|
' "email": null, '
|
|
' "list": [6, "seven", true, false, null, {"subkey2": 9}]'
|
|
' },'
|
|
' "repeatedValue": [{"age": 11}, {}]'
|
|
'}'
|
|
),
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestStruct()
|
|
self.CheckParseBack(message, parsed_message)
|
|
# check for regression; this used to raise
|
|
_ = parsed_message.value['empty_struct']
|
|
_ = parsed_message.value['empty_list']
|
|
|
|
def testValueMessage(self):
|
|
message = json_format_proto3_pb2.TestValue()
|
|
message.value.string_value = 'hello'
|
|
message.repeated_value.add().number_value = 11.1
|
|
message.repeated_value.add().bool_value = False
|
|
message.repeated_value.add().null_value = 0
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=False
|
|
)
|
|
),
|
|
json.loads(
|
|
'{ "value": "hello", "repeatedValue": [11.1, false, null]}'
|
|
),
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestValue()
|
|
self.CheckParseBack(message, parsed_message)
|
|
# Can't parse back if the Value message is not set.
|
|
message.repeated_value.add()
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=False
|
|
)
|
|
),
|
|
json.loads(
|
|
'{ "value": "hello", "repeatedValue": [11.1, false, null, null]}'
|
|
),
|
|
)
|
|
message.Clear()
|
|
json_format.Parse('{"value": null}', message)
|
|
self.assertEqual(message.value.WhichOneof('kind'), 'null_value')
|
|
|
|
def testValueMessageErrors(self):
|
|
message = json_format_proto3_pb2.TestValue()
|
|
message.value.number_value = math.inf
|
|
with self.assertRaises(json_format.SerializeToJsonError) as cm:
|
|
json_format.MessageToJson(message)
|
|
self.assertEqual(
|
|
'Failed to serialize value field: Fail to serialize Infinity for '
|
|
'Value.number_value, which would parse as string_value.',
|
|
str(cm.exception),
|
|
)
|
|
message.value.number_value = math.nan
|
|
with self.assertRaises(json_format.SerializeToJsonError) as cm:
|
|
json_format.MessageToJson(message)
|
|
self.assertEqual(
|
|
'Failed to serialize value field: Fail to serialize NaN for '
|
|
'Value.number_value, which would parse as string_value.',
|
|
str(cm.exception),
|
|
)
|
|
|
|
def testListValueMessage(self):
|
|
message = json_format_proto3_pb2.TestListValue()
|
|
message.value.values.add().number_value = 11.1
|
|
message.value.values.add().null_value = 0
|
|
message.value.values.add().bool_value = True
|
|
message.value.values.add().string_value = 'hello'
|
|
message.value.values.add().struct_value['name'] = 'Jim'
|
|
message.repeated_value.add().values.add().number_value = 1
|
|
message.repeated_value.add()
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=False
|
|
)
|
|
),
|
|
json.loads(
|
|
'{"value": [11.1, null, true, "hello", {"name": "Jim"}]\n,'
|
|
'"repeatedValue": [[1], []]}'
|
|
),
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestListValue()
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
def testNullValue(self):
|
|
message = json_format_proto3_pb2.TestOneof()
|
|
message.oneof_null_value = 0
|
|
self.assertEqual(
|
|
json_format.MessageToJson(message), '{\n "oneofNullValue": null\n}'
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestOneof()
|
|
self.CheckParseBack(message, parsed_message)
|
|
# Check old format is also accepted
|
|
new_message = json_format_proto3_pb2.TestOneof()
|
|
json_format.Parse('{\n "oneofNullValue": "NULL_VALUE"\n}', new_message)
|
|
self.assertEqual(
|
|
json_format.MessageToJson(new_message), '{\n "oneofNullValue": null\n}'
|
|
)
|
|
|
|
def testAnyMessage(self):
|
|
message = json_format_proto3_pb2.TestAny()
|
|
value1 = json_format_proto3_pb2.MessageType()
|
|
value2 = json_format_proto3_pb2.MessageType()
|
|
value1.value = 1234
|
|
value2.value = 5678
|
|
message.value.Pack(value1)
|
|
message.repeated_value.add().Pack(value1)
|
|
message.repeated_value.add().Pack(value2)
|
|
message.repeated_value.add()
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
)
|
|
),
|
|
json.loads(
|
|
'{\n'
|
|
' "repeatedValue": [ {\n'
|
|
' "@type": "type.googleapis.com/proto3.MessageType",\n'
|
|
' "value": 1234\n'
|
|
' }, {\n'
|
|
' "@type": "type.googleapis.com/proto3.MessageType",\n'
|
|
' "value": 5678\n'
|
|
' },\n'
|
|
' {}],\n'
|
|
' "value": {\n'
|
|
' "@type": "type.googleapis.com/proto3.MessageType",\n'
|
|
' "value": 1234\n'
|
|
' }\n'
|
|
'}\n'
|
|
),
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestAny()
|
|
self.CheckParseBack(message, parsed_message)
|
|
# Must print @type first
|
|
test_message = json_format_proto3_pb2.TestMessage(
|
|
bool_value=True,
|
|
int32_value=20,
|
|
int64_value=-20,
|
|
uint32_value=20,
|
|
uint64_value=20,
|
|
double_value=3.14,
|
|
string_value='foo',
|
|
)
|
|
message.Clear()
|
|
message.value.Pack(test_message)
|
|
self.assertEqual(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=False
|
|
)[0:68],
|
|
'{\n'
|
|
' "value": {\n'
|
|
' "@type": "type.googleapis.com/proto3.TestMessage"',
|
|
)
|
|
|
|
def testAnyMessageDescriptorPoolMissingType(self):
|
|
packed_message = unittest_pb2.OneString()
|
|
packed_message.data = 'string'
|
|
message = any_test_pb2.TestAny()
|
|
message.any_value.Pack(packed_message)
|
|
empty_pool = descriptor_pool.DescriptorPool()
|
|
with self.assertRaises(TypeError) as cm:
|
|
json_format.MessageToJson(
|
|
message,
|
|
always_print_fields_with_no_presence=True,
|
|
descriptor_pool=empty_pool,
|
|
)
|
|
self.assertEqual(
|
|
'Can not find message descriptor by type_url:'
|
|
' type.googleapis.com/proto2_unittest.OneString',
|
|
str(cm.exception),
|
|
)
|
|
|
|
def testWellKnownInAnyMessage(self):
|
|
message = any_pb2.Any()
|
|
int32_value = wrappers_pb2.Int32Value()
|
|
int32_value.value = 1234
|
|
message.Pack(int32_value)
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
)
|
|
),
|
|
json.loads(
|
|
'{\n'
|
|
' "@type": "type.googleapis.com/google.protobuf.Int32Value",\n'
|
|
' "value": 1234\n'
|
|
'}\n'
|
|
),
|
|
)
|
|
parsed_message = any_pb2.Any()
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
timestamp = timestamp_pb2.Timestamp()
|
|
message.Pack(timestamp)
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
)
|
|
),
|
|
json.loads(
|
|
'{\n'
|
|
' "@type": "type.googleapis.com/google.protobuf.Timestamp",\n'
|
|
' "value": "1970-01-01T00:00:00Z"\n'
|
|
'}\n'
|
|
),
|
|
)
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
duration = duration_pb2.Duration()
|
|
duration.seconds = 1
|
|
message.Pack(duration)
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
)
|
|
),
|
|
json.loads(
|
|
'{\n'
|
|
' "@type": "type.googleapis.com/google.protobuf.Duration",\n'
|
|
' "value": "1s"\n'
|
|
'}\n'
|
|
),
|
|
)
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
field_mask = field_mask_pb2.FieldMask()
|
|
field_mask.paths.append('foo.bar')
|
|
field_mask.paths.append('bar')
|
|
message.Pack(field_mask)
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
)
|
|
),
|
|
json.loads(
|
|
'{\n'
|
|
' "@type": "type.googleapis.com/google.protobuf.FieldMask",\n'
|
|
' "value": "foo.bar,bar"\n'
|
|
'}\n'
|
|
),
|
|
)
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
struct_message = struct_pb2.Struct()
|
|
struct_message['name'] = 'Jim'
|
|
message.Pack(struct_message)
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
)
|
|
),
|
|
json.loads(
|
|
'{\n'
|
|
' "@type": "type.googleapis.com/google.protobuf.Struct",\n'
|
|
' "value": {"name": "Jim"}\n'
|
|
'}\n'
|
|
),
|
|
)
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
nested_any = any_pb2.Any()
|
|
int32_value.value = 5678
|
|
nested_any.Pack(int32_value)
|
|
message.Pack(nested_any)
|
|
self.assertEqual(
|
|
json.loads(
|
|
json_format.MessageToJson(
|
|
message, always_print_fields_with_no_presence=True
|
|
)
|
|
),
|
|
json.loads(
|
|
'{\n'
|
|
' "@type": "type.googleapis.com/google.protobuf.Any",\n'
|
|
' "value": {\n'
|
|
' "@type": "type.googleapis.com/google.protobuf.Int32Value",\n'
|
|
' "value": 5678\n'
|
|
' }\n'
|
|
'}\n'
|
|
),
|
|
)
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
def testParseNull(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
parsed_message = json_format_proto3_pb2.TestMessage()
|
|
self.FillAllFields(parsed_message)
|
|
json_format.Parse(
|
|
'{"int32Value": null, '
|
|
'"int64Value": null, '
|
|
'"uint32Value": null,'
|
|
'"uint64Value": null,'
|
|
'"floatValue": null,'
|
|
'"doubleValue": null,'
|
|
'"boolValue": null,'
|
|
'"stringValue": null,'
|
|
'"bytesValue": null,'
|
|
'"messageValue": null,'
|
|
'"enumValue": null,'
|
|
'"repeatedInt32Value": null,'
|
|
'"repeatedInt64Value": null,'
|
|
'"repeatedUint32Value": null,'
|
|
'"repeatedUint64Value": null,'
|
|
'"repeatedFloatValue": null,'
|
|
'"repeatedDoubleValue": null,'
|
|
'"repeatedBoolValue": null,'
|
|
'"repeatedStringValue": null,'
|
|
'"repeatedBytesValue": null,'
|
|
'"repeatedMessageValue": null,'
|
|
'"repeatedEnumValue": null'
|
|
'}',
|
|
parsed_message,
|
|
)
|
|
self.assertEqual(message, parsed_message)
|
|
# Null and {} should have different behavior for sub message.
|
|
self.assertFalse(parsed_message.HasField('message_value'))
|
|
json_format.Parse('{"messageValue": {}}', parsed_message)
|
|
self.assertTrue(parsed_message.HasField('message_value'))
|
|
# Null is not allowed to be used as an element in repeated field.
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
r'Failed to parse repeatedInt32Value field: '
|
|
r'null is not allowed to be used as an element in a repeated field '
|
|
r'at TestMessage.repeatedInt32Value\[1\].',
|
|
json_format.Parse,
|
|
'{"repeatedInt32Value":[1, null]}',
|
|
parsed_message,
|
|
)
|
|
self.CheckError(
|
|
'{"repeatedMessageValue":[null]}',
|
|
r'Failed to parse repeatedMessageValue field: null is not'
|
|
r' allowed to be used as an element in a repeated field '
|
|
r'at TestMessage.repeatedMessageValue\[0\].',
|
|
)
|
|
|
|
def testNanFloat(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
message.float_value = float('nan')
|
|
text = '{\n "floatValue": "NaN"\n}'
|
|
self.assertEqual(json_format.MessageToJson(message), text)
|
|
parsed_message = json_format_proto3_pb2.TestMessage()
|
|
json_format.Parse(text, parsed_message)
|
|
self.assertTrue(math.isnan(parsed_message.float_value))
|
|
|
|
def testParseDoubleToFloat(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
text = '{"repeatedDoubleValue": [3.4028235e+39, 1.4028235e-39]\n}'
|
|
json_format.Parse(text, message)
|
|
self.assertEqual(message.repeated_double_value[0], 3.4028235e39)
|
|
self.assertEqual(message.repeated_double_value[1], 1.4028235e-39)
|
|
text = '{"repeatedFloatValue": [3.4028235e+39, 1.4028235e-39]\n}'
|
|
self.CheckError(
|
|
text,
|
|
r'Failed to parse repeatedFloatValue field: '
|
|
r'Float value too large at TestMessage.repeatedFloatValue\[0\].',
|
|
)
|
|
|
|
def testFloatPrecision(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
|
|
# Default float_precision will automatic print shortest float.
|
|
message.float_value = 1.1000000011
|
|
text = '{\n "floatValue": 1.1\n}'
|
|
self.assertEqual(json_format.MessageToJson(message), text)
|
|
message.float_value = 1.00000075e-36
|
|
text = '{\n "floatValue": 1.00000075e-36\n}'
|
|
self.assertEqual(json_format.MessageToJson(message), text)
|
|
message.float_value = 12345678912345e11
|
|
text = '{\n "floatValue": 1.234568e+24\n}'
|
|
self.assertEqual(json_format.MessageToJson(message), text)
|
|
|
|
# Test a bunch of data and check json encode/decode do not
|
|
# lose precision
|
|
value_list = [0x00, 0xD8, 0x6E, 0x00]
|
|
msg2 = json_format_proto3_pb2.TestMessage()
|
|
for a in range(0, 256):
|
|
value_list[3] = a
|
|
for b in range(0, 256):
|
|
value_list[0] = b
|
|
byte_array = bytearray(value_list)
|
|
message.float_value = struct.unpack('<f', byte_array)[0]
|
|
self.CheckParseBack(message, msg2)
|
|
|
|
def testParseEmptyText(self):
|
|
self.CheckError('', r'Failed to load JSON: (Expecting value)|(No JSON).')
|
|
|
|
def testParseEnumValue(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
text = '{"enumValue": 0}'
|
|
json_format.Parse(text, message)
|
|
text = '{"enumValue": 1}'
|
|
json_format.Parse(text, message)
|
|
self.CheckError(
|
|
'{"enumValue": "baz"}',
|
|
'Failed to parse enumValue field: Invalid enum value baz '
|
|
'for enum type proto3.EnumType at TestMessage.enumValue.',
|
|
)
|
|
# Proto3 accepts numeric unknown enums.
|
|
text = '{"enumValue": 12345}'
|
|
json_format.Parse(text, message)
|
|
# Proto2 does not accept numeric unknown enums.
|
|
message = unittest_pb2.TestAllTypes()
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Failed to parse optionalNestedEnum field: Invalid enum value 12345 '
|
|
'for enum type proto2_unittest.TestAllTypes.NestedEnum at '
|
|
'TestAllTypes.optionalNestedEnum.',
|
|
json_format.Parse,
|
|
'{"optionalNestedEnum": 12345}',
|
|
message,
|
|
)
|
|
|
|
def testParseUnknownEnumStringValue_Scalar_Proto2(self):
|
|
message = json_format_pb2.TestNumbers()
|
|
text = '{"a": "UNKNOWN_STRING_VALUE"}'
|
|
json_format.Parse(text, message, ignore_unknown_fields=True)
|
|
|
|
self.assertFalse(message.HasField('a'))
|
|
|
|
def testParseErrorForUnknownEnumValue_ScalarWithoutIgnore_Proto2(self):
|
|
message = json_format_pb2.TestNumbers()
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Invalid enum value',
|
|
json_format.Parse,
|
|
'{"a": "UNKNOWN_STRING_VALUE"}',
|
|
message,
|
|
)
|
|
|
|
def testParseUnknownEnumStringValue_Repeated_Proto2(self):
|
|
message = json_format_pb2.TestRepeatedEnum()
|
|
text = '{"repeatedEnum": ["UNKNOWN_STRING_VALUE", "BUFFER"]}'
|
|
json_format.Parse(text, message, ignore_unknown_fields=True)
|
|
|
|
self.assertEqual(len(message.repeated_enum), 1)
|
|
self.assertTrue(message.repeated_enum[0] == json_format_pb2.BUFFER)
|
|
|
|
def testParseUnknownEnumStringValue_Map_Proto2(self):
|
|
message = json_format_pb2.TestMapOfEnums()
|
|
text = '{"enumMap": {"key1": "BUFFER", "key2": "UNKNOWN_STRING_VALUE"}}'
|
|
json_format.Parse(text, message, ignore_unknown_fields=True)
|
|
|
|
self.assertTrue(message.enum_map['key1'] == json_format_pb2.BUFFER)
|
|
self.assertFalse('key2' in message.enum_map)
|
|
|
|
def testParseUnknownEnumStringValue_ExtensionField_Proto2(self):
|
|
message = json_format_pb2.TestMessageWithExtension()
|
|
text = """
|
|
{"[proto2_unittest.TestExtension.enum_ext]": "UNKNOWN_STRING_VALUE"}
|
|
"""
|
|
json_format.Parse(text, message, ignore_unknown_fields=True)
|
|
|
|
self.assertFalse(
|
|
json_format_pb2.TestExtension.enum_ext in message.Extensions
|
|
)
|
|
|
|
def testParseUnknownEnumStringValue_ExtensionFieldWithoutIgnore_Proto2(self):
|
|
message = json_format_pb2.TestMessageWithExtension()
|
|
text = """
|
|
{"[proto2_unittest.TestExtension.enum_ext]": "UNKNOWN_STRING_VALUE"}
|
|
"""
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Invalid enum value',
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
|
|
def testParseUnknownEnumStringValue_Scalar_Proto3(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
text = '{"enumValue": "UNKNOWN_STRING_VALUE"}'
|
|
|
|
json_format.Parse(text, message, ignore_unknown_fields=True)
|
|
self.assertEqual(message.enum_value, 0)
|
|
|
|
def testParseUnknownEnumStringValue_Repeated_Proto3(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
text = '{"repeatedEnumValue": ["UNKNOWN_STRING_VALUE", "FOO"]}'
|
|
json_format.Parse(text, message, ignore_unknown_fields=True)
|
|
|
|
self.assertEqual(len(message.repeated_enum_value), 1)
|
|
self.assertTrue(
|
|
message.repeated_enum_value[0] == json_format_proto3_pb2.FOO
|
|
)
|
|
|
|
def testParseUnknownEnumStringValue_Map_Proto3(self):
|
|
message = json_format_proto3_pb2.MapOfEnums()
|
|
text = '{"map": {"key1": "FOO", "key2": "UNKNOWN_STRING_VALUE"}}'
|
|
json_format.Parse(text, message, ignore_unknown_fields=True)
|
|
|
|
self.assertTrue(message.map['key1'] == json_format_proto3_pb2.FOO)
|
|
self.assertFalse('key2' in message.map)
|
|
|
|
def testBytes(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
# Test url base64
|
|
text = '{"bytesValue": "-_"}'
|
|
json_format.Parse(text, message)
|
|
self.assertEqual(message.bytes_value, b'\xfb')
|
|
# Test padding
|
|
text = '{"bytesValue": "AQI="}'
|
|
json_format.Parse(text, message)
|
|
self.assertEqual(message.bytes_value, b'\x01\x02')
|
|
text = '{"bytesValue": "AQI"}'
|
|
json_format.Parse(text, message)
|
|
self.assertEqual(message.bytes_value, b'\x01\x02')
|
|
text = '{"bytesValue": "AQI*"}'
|
|
json_format.Parse(text, message)
|
|
self.assertEqual(message.bytes_value, b'\x01\x02')
|
|
|
|
def testParseBadIdentifier(self):
|
|
self.CheckError(
|
|
'{int32Value: 1}',
|
|
(
|
|
r'Failed to load JSON: Expecting property name'
|
|
r'( enclosed in double quotes)?: line 1'
|
|
),
|
|
)
|
|
self.CheckError(
|
|
'{"unknownName": 1}',
|
|
'Message type "proto3.TestMessage" has no field named '
|
|
'"unknownName" at "TestMessage".',
|
|
)
|
|
|
|
def testIgnoreUnknownField(self):
|
|
text = '{"unknownName": 1}'
|
|
parsed_message = json_format_proto3_pb2.TestMessage()
|
|
json_format.Parse(text, parsed_message, ignore_unknown_fields=True)
|
|
text = (
|
|
'{\n'
|
|
' "repeatedValue": [ {\n'
|
|
' "@type": "type.googleapis.com/proto3.MessageType",\n'
|
|
' "unknownName": 1\n'
|
|
' }]\n'
|
|
'}\n'
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestAny()
|
|
json_format.Parse(text, parsed_message, ignore_unknown_fields=True)
|
|
|
|
def testDuplicateField(self):
|
|
self.CheckError(
|
|
'{"int32Value": 1,\n"int32Value":2}',
|
|
'Failed to load JSON: duplicate key int32Value.',
|
|
)
|
|
|
|
def testDuplicateFieldAlternateNames(self):
|
|
# Note: this behavior is non-spec and an oversight bug in the
|
|
# implementation, but would be a breaking change to fix. The duplicate field
|
|
# checker intends reject inputs with duplicate key names, but it only
|
|
# catches keys that are exact matches and not alternate spellings that
|
|
# correspond to the same field.
|
|
parsed_message = json_format_proto3_pb2.TestMessage()
|
|
json_format.Parse('{"int32Value": 1,"int32_value":2}', parsed_message)
|
|
self.assertEqual(parsed_message.int32_value, 2)
|
|
|
|
def testDuplicateFieldAlternateNamesMap(self):
|
|
# Note: this behavior is non-spec and an oversight bug in the
|
|
# implementation, but would be a breaking change to fix. The duplicate field
|
|
# checker intends reject inputs with duplicate key names, but it only
|
|
# catches keys that are exact matches and not alternate spellings that
|
|
# correspond to the same field.
|
|
parsed_message = json_format_proto3_pb2.TestMap()
|
|
json_format.Parse(
|
|
'{"int32Map": {"1": 2}, "int32_map": {"3": 4}}', parsed_message
|
|
)
|
|
self.assertEqual(parsed_message.int32_map, {3: 4})
|
|
|
|
def testInvalidBoolValue(self):
|
|
self.CheckError(
|
|
'{"boolValue": 1}',
|
|
'Failed to parse boolValue field: '
|
|
'Expected true or false without quotes at TestMessage.boolValue.',
|
|
)
|
|
self.CheckError(
|
|
'{"boolValue": "true"}',
|
|
'Failed to parse boolValue field: '
|
|
'Expected true or false without quotes at TestMessage.boolValue.',
|
|
)
|
|
|
|
def testInvalidIntegerValue(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
text = '{"int32Value": 0x12345}'
|
|
self.assertRaises(json_format.ParseError, json_format.Parse, text, message)
|
|
self.CheckError(
|
|
'{"int32Value": 1.5}',
|
|
'Failed to parse int32Value field: '
|
|
"Couldn't parse integer: 1.5 at TestMessage.int32Value.",
|
|
)
|
|
self.CheckError(
|
|
'{"int32Value": "1.5"}',
|
|
'Failed to parse int32Value field: '
|
|
'Couldn\'t parse non-integer string: "1.5" at TestMessage.int32Value.',
|
|
)
|
|
self.CheckError(
|
|
'{"int32Value": "foo"}',
|
|
'Failed to parse int32Value field: invalid literal for int\\(\\) with'
|
|
" base 10: 'foo'.",
|
|
)
|
|
self.CheckError(
|
|
'{"int32Value": 012345}',
|
|
(r'Failed to load JSON: Expecting \'?,\'? delimiter: ' r'line 1.'),
|
|
)
|
|
self.CheckError(
|
|
'{"int32Value": " 1 "}',
|
|
'Failed to parse int32Value field: '
|
|
'Couldn\'t parse integer: " 1 " at TestMessage.int32Value.',
|
|
)
|
|
self.CheckError(
|
|
'{"int32Value": "1 "}',
|
|
'Failed to parse int32Value field: '
|
|
'Couldn\'t parse integer: "1 " at TestMessage.int32Value.',
|
|
)
|
|
self.CheckError(
|
|
'{"int32Value": false}',
|
|
'Failed to parse int32Value field: Bool value False '
|
|
'is not acceptable for integer field at TestMessage.int32Value.',
|
|
)
|
|
self.CheckError(
|
|
'{"int32Value": 12345678901234567890}',
|
|
'Failed to parse int32Value field: Value out of range: '
|
|
'12345678901234567890.',
|
|
)
|
|
self.CheckError(
|
|
'{"uint32Value": -1}',
|
|
'Failed to parse uint32Value field: Value out of range: -1.',
|
|
)
|
|
|
|
def testInvalidFloatValue(self):
|
|
self.CheckError(
|
|
'{"floatValue": "nan"}',
|
|
"Failed to parse floatValue field: Couldn't "
|
|
'parse float "nan", use "NaN" instead at TestMessage.floatValue.',
|
|
)
|
|
self.CheckError(
|
|
'{"floatValue": NaN}',
|
|
"Failed to parse floatValue field: Couldn't "
|
|
'parse NaN, use quoted "NaN" instead.',
|
|
)
|
|
self.CheckError(
|
|
'{"floatValue": Infinity}',
|
|
"Failed to parse floatValue field: Couldn't parse Infinity"
|
|
' or value too large, use quoted "Infinity" instead.',
|
|
)
|
|
self.CheckError(
|
|
'{"floatValue": -Infinity}',
|
|
"Failed to parse floatValue field: Couldn't parse "
|
|
'-Infinity or value too small, '
|
|
'use quoted "-Infinity" instead.',
|
|
)
|
|
self.CheckError(
|
|
'{"doubleValue": -1.89769e+308}',
|
|
"Failed to parse doubleValue field: Couldn't parse "
|
|
'-Infinity or value too small, '
|
|
'use quoted "-Infinity" instead.',
|
|
)
|
|
self.CheckError(
|
|
'{"floatValue": 3.4028235e+39}',
|
|
'Failed to parse floatValue field: Float value too large.',
|
|
)
|
|
self.CheckError(
|
|
'{"floatValue": -3.502823e+38}',
|
|
'Failed to parse floatValue field: Float value too small.',
|
|
)
|
|
|
|
def testInvalidRepeated(self):
|
|
self.CheckError(
|
|
'{"repeatedInt32Value": 12345}',
|
|
(
|
|
r'Failed to parse repeatedInt32Value field: repeated field'
|
|
r' repeatedInt32Value must be in \[\] which is 12345 at'
|
|
r' TestMessage.'
|
|
),
|
|
)
|
|
|
|
def testInvalidMap(self):
|
|
message = json_format_proto3_pb2.TestMap()
|
|
text = '{"int32Map": {"null": 2, "2": 3}}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Failed to parse int32Map field: invalid literal',
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
text = '{"int32Map": {1: 2, "2": 3}}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
(
|
|
r'Failed to load JSON: Expecting property name'
|
|
r'( enclosed in double quotes)?: line 1'
|
|
),
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
text = '{"boolMap": {"null": 1}}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Failed to parse boolMap field: Expected "true" or "false", not null at'
|
|
' TestMap.boolMap.key',
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
text = r'{"stringMap": {"a": 3, "\u0061": 2}}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Failed to load JSON: duplicate key a',
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
text = r'{"stringMap": 0}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Failed to parse stringMap field: Map field string_map must be '
|
|
'in a dict which is 0 at TestMap.stringMap.',
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
|
|
def testInvalidTimestamp(self):
|
|
message = json_format_proto3_pb2.TestTimestamp()
|
|
text = '{"value": "10000-01-01T00:00:00.00Z"}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Failed to parse value field: '
|
|
"time data '10000-01-01T00:00:00' does not match"
|
|
" format '%Y-%m-%dT%H:%M:%S' at TestTimestamp.value.",
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
text = '{"value": "1970-01-01T00:00:00.0123456789012Z"}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'nanos 0123456789012 more than 9 fractional digits.',
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
text = '{"value": "1972-01-01T01:00:00.01+08"}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
r'Invalid timezone offset value: \+08.',
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
# Time smaller than minimum time.
|
|
text = '{"value": "0000-01-01T00:00:00Z"}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Failed to parse value field: year ',
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
# Time bigger than maximum time.
|
|
message.value.seconds = 253402300800
|
|
self.assertRaisesRegex(json_format.SerializeToJsonError,
|
|
'Timestamp is not valid',
|
|
json_format.MessageToJson, message)
|
|
# Nanos smaller than 0
|
|
message.value.seconds = 0
|
|
message.value.nanos = -1
|
|
self.assertRaisesRegex(
|
|
json_format.SerializeToJsonError,
|
|
'Timestamp is not valid',
|
|
json_format.MessageToJson,
|
|
message,
|
|
)
|
|
# Lower case t does not accept.
|
|
text = '{"value": "0001-01-01t00:00:00Z"}'
|
|
with self.assertRaises(json_format.ParseError) as e:
|
|
json_format.Parse(text, message)
|
|
self.assertEqual(
|
|
'Failed to parse value field: '
|
|
"time data '0001-01-01t00:00:00' does not match format "
|
|
"'%Y-%m-%dT%H:%M:%S', lowercase 't' is not accepted "
|
|
'at TestTimestamp.value.',
|
|
str(e.exception),
|
|
)
|
|
|
|
def testInvalidOneof(self):
|
|
message = json_format_proto3_pb2.TestOneof()
|
|
text = '{"oneofInt32Value": 1, "oneofStringValue": "2"}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Message type "proto3.TestOneof"'
|
|
' should not have multiple "oneof_value" oneof fields at "TestOneof".',
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
|
|
def testInvalidListValue(self):
|
|
message = json_format_proto3_pb2.TestListValue()
|
|
text = '{"value": 1234}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
r'Failed to parse value field: ListValue must be in \[\] which is '
|
|
'1234 at TestListValue.value.',
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
|
|
class UnknownClass(object):
|
|
|
|
def __str__(self):
|
|
return 'v'
|
|
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
r' at TestListValue.value\[1\].fake.',
|
|
json_format.ParseDict,
|
|
{'value': ['hello', {'fake': UnknownClass()}]},
|
|
message,
|
|
)
|
|
|
|
def testInvalidStruct(self):
|
|
message = json_format_proto3_pb2.TestStruct()
|
|
text = '{"value": 1234}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Failed to parse value field: Struct must be in a dict which is '
|
|
'1234 at TestStruct.value',
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
|
|
def testTimestampInvalidStringValue(self):
|
|
message = json_format_proto3_pb2.TestTimestamp()
|
|
text = '{"value": {"foo": 123}}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
r"Timestamp JSON value not a string: {u?'foo': 123}",
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
|
|
def testDurationInvalidStringValue(self):
|
|
message = json_format_proto3_pb2.TestDuration()
|
|
text = '{"value": {"foo": 123}}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
r"Duration JSON value not a string: {u?'foo': 123}",
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
|
|
def testFieldMaskInvalidStringValue(self):
|
|
message = json_format_proto3_pb2.TestFieldMask()
|
|
text = '{"value": {"foo": 123}}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
r"FieldMask JSON value not a string: {u?'foo': 123}",
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
|
|
def testInvalidAny(self):
|
|
message = any_pb2.Any()
|
|
text = '{"@type": "type.googleapis.com/google.protobuf.Int32Value"}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
"KeyError: 'value'",
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
text = '{"value": 1234}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'@type is missing when parsing any message at Any',
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
text = '{"@type": "type.googleapis.com/MessageNotExist", "value": 1234}'
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Can not find message descriptor by type_url: '
|
|
'type.googleapis.com/MessageNotExist at Any',
|
|
json_format.Parse,
|
|
text,
|
|
message,
|
|
)
|
|
# Only last part is to be used: b/25630112
|
|
text = (
|
|
r'{"@type": "incorrect.googleapis.com/google.protobuf.Int32Value",'
|
|
r'"value": 1234}'
|
|
)
|
|
json_format.Parse(text, message)
|
|
|
|
def testPreservingProtoFieldNames(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
message.int32_value = 12345
|
|
self.assertEqual(
|
|
'{\n "int32Value": 12345\n}', json_format.MessageToJson(message)
|
|
)
|
|
self.assertEqual(
|
|
'{\n "int32_value": 12345\n}',
|
|
json_format.MessageToJson(
|
|
message,
|
|
always_print_fields_with_no_presence=False,
|
|
preserving_proto_field_name=True,
|
|
),
|
|
)
|
|
# When always_print_fields_with_no_presence is True.
|
|
message = json_format_proto3_pb2.TestTimestamp()
|
|
self.assertEqual(
|
|
'{\n "repeatedValue": []\n}',
|
|
json_format.MessageToJson(
|
|
message,
|
|
always_print_fields_with_no_presence=True,
|
|
),
|
|
)
|
|
self.assertEqual(
|
|
'{\n "repeated_value": []\n}',
|
|
json_format.MessageToJson(
|
|
message,
|
|
always_print_fields_with_no_presence=True,
|
|
preserving_proto_field_name=True,
|
|
),
|
|
)
|
|
|
|
# Parsers accept both original proto field names and lowerCamelCase names.
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
json_format.Parse('{"int32Value": 54321}', message)
|
|
self.assertEqual(54321, message.int32_value)
|
|
json_format.Parse('{"int32_value": 12345}', message)
|
|
self.assertEqual(12345, message.int32_value)
|
|
|
|
def testIndent(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
message.int32_value = 12345
|
|
self.assertEqual(
|
|
'{\n"int32Value": 12345\n}',
|
|
json_format.MessageToJson(message, indent=0),
|
|
)
|
|
|
|
def testFormatEnumsAsInts(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
message.enum_value = json_format_proto3_pb2.BAR
|
|
message.repeated_enum_value.append(json_format_proto3_pb2.FOO)
|
|
message.repeated_enum_value.append(json_format_proto3_pb2.BAR)
|
|
self.assertEqual(
|
|
json.loads('{\n "enumValue": 1,\n "repeatedEnumValue": [0, 1]\n}\n'),
|
|
json.loads(
|
|
json_format.MessageToJson(message, use_integers_for_enums=True)
|
|
),
|
|
)
|
|
|
|
def testParseDict(self):
|
|
expected = 12345
|
|
js_dict = {'int32Value': expected}
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
json_format.ParseDict(js_dict, message)
|
|
self.assertEqual(expected, message.int32_value)
|
|
|
|
def testParseDictAcceptsPairValueTuples(self):
|
|
expected = [1, 2, 3]
|
|
js_dict = {'repeatedInt32Value': (1, 2, 3)}
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
json_format.ParseDict(js_dict, message)
|
|
self.assertEqual(expected, message.repeated_int32_value)
|
|
|
|
def testParseDictAcceptsRepeatedValueTuples(self):
|
|
expected = json_format_proto3_pb2.TestListValue(
|
|
repeated_value=[
|
|
struct_pb2.ListValue(
|
|
values=[
|
|
struct_pb2.Value(number_value=4),
|
|
struct_pb2.Value(number_value=5),
|
|
]
|
|
),
|
|
struct_pb2.ListValue(values=[struct_pb2.Value(number_value=6)]),
|
|
]
|
|
)
|
|
js_dict = {'repeated_value': ((4, 5), (6,))}
|
|
message = json_format_proto3_pb2.TestListValue()
|
|
json_format.ParseDict(js_dict, message)
|
|
self.assertEqual(expected, message)
|
|
|
|
def testParseDictAnyDescriptorPoolMissingType(self):
|
|
# Confirm that ParseDict does not raise ParseError with default pool
|
|
js_dict = {
|
|
'any_value': {
|
|
'@type': 'type.googleapis.com/proto3.MessageType',
|
|
'value': 1234,
|
|
}
|
|
}
|
|
json_format.ParseDict(js_dict, any_test_pb2.TestAny())
|
|
# Check ParseDict raises ParseError with empty pool
|
|
js_dict = {
|
|
'any_value': {
|
|
'@type': 'type.googleapis.com/proto3.MessageType',
|
|
'value': 1234,
|
|
}
|
|
}
|
|
with self.assertRaises(json_format.ParseError) as cm:
|
|
empty_pool = descriptor_pool.DescriptorPool()
|
|
json_format.ParseDict(
|
|
js_dict, any_test_pb2.TestAny(), descriptor_pool=empty_pool
|
|
)
|
|
self.assertEqual(
|
|
str(cm.exception),
|
|
'Failed to parse any_value field: Can not find message descriptor by'
|
|
' type_url: type.googleapis.com/proto3.MessageType at '
|
|
'TestAny.any_value.',
|
|
)
|
|
|
|
def testParseDictNestedAnyDescriptorPoolMissingType(self):
|
|
# Confirm that ParseDict nondestructive with empty pool
|
|
js_dict = {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {
|
|
'@type': 'type.googleapis.com/proto2_unittest.TestAny',
|
|
'any_value': {
|
|
'@type': 'type.googleapis.com/UnknownMessageType',
|
|
},
|
|
},
|
|
}
|
|
js_dict_copy = json.loads(json.dumps(js_dict))
|
|
with self.assertRaises(json_format.ParseError) as cm:
|
|
json_format.ParseDict(js_dict_copy, any_pb2.Any())
|
|
self.assertEqual(
|
|
str(cm.exception),
|
|
'Failed to parse any_value field: Can not find message descriptor by'
|
|
' type_url: type.googleapis.com/UnknownMessageType at'
|
|
' Any.value.any_value.',
|
|
)
|
|
self.assertEqual(js_dict, js_dict_copy)
|
|
|
|
def testParseDictUnknownValueType(self):
|
|
class UnknownClass(object):
|
|
|
|
def __repr__(self):
|
|
return 'v'
|
|
|
|
message = json_format_proto3_pb2.TestValue()
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
r"Value v has unexpected type <class '.*\.UnknownClass'>.",
|
|
json_format.ParseDict,
|
|
{'value': UnknownClass()},
|
|
message,
|
|
)
|
|
|
|
def testMessageToDict(self):
|
|
message = json_format_proto3_pb2.TestMessage()
|
|
message.int32_value = 12345
|
|
expected = {'int32Value': 12345}
|
|
self.assertEqual(expected, json_format.MessageToDict(message))
|
|
|
|
def testJsonName(self):
|
|
message = json_format_proto3_pb2.TestCustomJsonName()
|
|
message.value = 12345
|
|
self.assertEqual(
|
|
'{\n "@value": 12345\n}', json_format.MessageToJson(message)
|
|
)
|
|
parsed_message = json_format_proto3_pb2.TestCustomJsonName()
|
|
self.CheckParseBack(message, parsed_message)
|
|
|
|
def testSortKeys(self):
|
|
# Testing sort_keys is not perfectly working, as by random luck we could
|
|
# get the output sorted. We just use a selection of names.
|
|
message = json_format_proto3_pb2.TestMessage(
|
|
bool_value=True,
|
|
int32_value=1,
|
|
int64_value=3,
|
|
uint32_value=4,
|
|
string_value='bla',
|
|
)
|
|
self.assertEqual(
|
|
json_format.MessageToJson(message, sort_keys=True),
|
|
# We use json.dumps() instead of a hardcoded string due to differences
|
|
# between Python 2 and Python 3.
|
|
json.dumps(
|
|
{
|
|
'boolValue': True,
|
|
'int32Value': 1,
|
|
'int64Value': '3',
|
|
'uint32Value': 4,
|
|
'stringValue': 'bla',
|
|
},
|
|
indent=2,
|
|
sort_keys=True,
|
|
),
|
|
)
|
|
|
|
def testNestedRecursiveLimit(self):
|
|
message = unittest_pb2.NestedTestAllTypes()
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Message too deep. Max recursion depth is 3',
|
|
json_format.Parse,
|
|
'{"child": {"child": {"child" : {}}}}',
|
|
message,
|
|
max_recursion_depth=3,
|
|
)
|
|
# The following one can pass
|
|
json_format.Parse(
|
|
'{"payload": {}, "child": {"child":{}}}', message, max_recursion_depth=3
|
|
)
|
|
|
|
def testStructRecursionDepthEnforcement(self):
|
|
"""Test that nested Struct messages respect max_recursion_depth limit."""
|
|
message = struct_pb2.Struct()
|
|
# With max_recursion_depth=5, we can nest up to depth 5.
|
|
# {"a": {"b": {"c": {}}}} will reach depth 6 when trying to parse "c" value.
|
|
# This is treated as 6 in our depth enforcement rather than depth 3 because
|
|
# it is Struct-Value-Struct-Value-Struct-Value.
|
|
nested_dict = {'a': {'b': {'c': {}}}}
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Message too deep. Max recursion depth is 5',
|
|
json_format.ParseDict,
|
|
nested_dict,
|
|
message,
|
|
max_recursion_depth=5,
|
|
)
|
|
|
|
# This should pass as it reaches depth 5.
|
|
shallow_dict = {'a': {'b': {}}}
|
|
json_format.ParseDict(shallow_dict, message, max_recursion_depth=5)
|
|
|
|
def testAnyRecursionDepthEnforcement(self):
|
|
"""Test that nested Any messages respect max_recursion_depth limit."""
|
|
# Test that deeply nested Any messages raise ParseError instead of
|
|
# bypassing the recursion limit. This prevents DoS via nested Any.
|
|
message = any_pb2.Any()
|
|
|
|
# Create nested Any structure that should exceed depth limit
|
|
# With max_recursion_depth=5, we can nest 4 Any messages
|
|
# (depth 1 = outer Any, depth 2-4 = nested Anys, depth 5 = final value)
|
|
nested_any = {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
# Should raise ParseError due to exceeding max depth, not RecursionError
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Message too deep. Max recursion depth is 5',
|
|
json_format.ParseDict,
|
|
nested_any,
|
|
message,
|
|
max_recursion_depth=5,
|
|
)
|
|
|
|
# Verify that Any messages within the limit can be parsed successfully
|
|
# With max_recursion_depth=5, we can nest up to 4 Any messages
|
|
shallow_any = {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
json_format.ParseDict(shallow_any, message, max_recursion_depth=5)
|
|
|
|
def testAnyRecursionDepthBoundary(self):
|
|
"""Test recursion depth boundary behavior (exclusive upper limit)."""
|
|
message = any_pb2.Any()
|
|
|
|
# Create nested Any at depth exactly 4 (should succeed with max_recursion_depth=5)
|
|
depth_4_any = {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
# This should succeed: depth 4 < max_recursion_depth 5
|
|
json_format.ParseDict(depth_4_any, message, max_recursion_depth=5)
|
|
|
|
# Create nested Any at depth exactly 5 (should fail with max_recursion_depth=5)
|
|
depth_5_any = {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {
|
|
'@type': 'type.googleapis.com/google.protobuf.Any',
|
|
'value': {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
# This should fail: depth 5 == max_recursion_depth 5 (exclusive limit)
|
|
self.assertRaisesRegex(
|
|
json_format.ParseError,
|
|
'Message too deep. Max recursion depth is 5',
|
|
json_format.ParseDict,
|
|
depth_5_any,
|
|
message,
|
|
max_recursion_depth=5,
|
|
)
|
|
|
|
def testJsonNameConflictSerilize(self):
|
|
message = more_messages_pb2.ConflictJsonName(value=2)
|
|
self.assertEqual(
|
|
json.loads('{"old_value": 2}'),
|
|
json.loads(json_format.MessageToJson(message)),
|
|
)
|
|
|
|
new_message = more_messages_pb2.ConflictJsonName(new_value=2)
|
|
self.assertEqual(
|
|
json.loads('{"value": 2}'),
|
|
json.loads(json_format.MessageToJson(new_message)),
|
|
)
|
|
|
|
def testJsonNameConflictParse(self):
|
|
message = more_messages_pb2.ConflictJsonName()
|
|
json_format.Parse('{"value": 2}', message)
|
|
self.assertEqual(2, message.new_value)
|
|
self.assertEqual(0, message.value)
|
|
|
|
def testJsonNameConflictRoundTrip(self):
|
|
message = more_messages_pb2.ConflictJsonName(value=2)
|
|
parsed_message = more_messages_pb2.ConflictJsonName()
|
|
json_string = json_format.MessageToJson(message)
|
|
json_format.Parse(json_string, parsed_message)
|
|
self.assertEqual(message, parsed_message)
|
|
|
|
new_message = more_messages_pb2.ConflictJsonName(new_value=2)
|
|
new_parsed_message = more_messages_pb2.ConflictJsonName()
|
|
json_string = json_format.MessageToJson(new_message)
|
|
json_format.Parse(json_string, new_parsed_message)
|
|
self.assertEqual(new_message, new_parsed_message)
|
|
|
|
def testOtherParseErrors(self):
|
|
self.CheckError(
|
|
'9',
|
|
"Failed to parse JSON: TypeError: 'int' object is not iterable.",
|
|
)
|
|
|
|
def testManyRecursionsRaisesParseError(self):
|
|
num_recursions = 1050
|
|
text = ('{"a":' * num_recursions) + '""' + ('}' * num_recursions)
|
|
with self.assertRaises(json_format.ParseError):
|
|
json_format.Parse(text, json_format_proto3_pb2.TestMessage())
|
|
|
|
@parameterized.named_parameters(
|
|
(
|
|
'Default',
|
|
json_enumval_custom_string_pb2.Armor.ARMOR_GORGET,
|
|
'ARMOR_GORGET',
|
|
),
|
|
(
|
|
'CustomString',
|
|
json_enumval_custom_string_pb2.Armor.ARMOR_GREAT_HELM,
|
|
'gr8 helm',
|
|
),
|
|
(
|
|
'QuoteInMiddle',
|
|
json_enumval_custom_string_pb2.Armor.ARMOR_GAUNTLET,
|
|
'a"b',
|
|
),
|
|
(
|
|
'DoubleQuote',
|
|
json_enumval_custom_string_pb2.Armor.ARMOR_PLATE,
|
|
'"plate"',
|
|
),
|
|
('EmptyString', json_enumval_custom_string_pb2.Armor.ARMOR_COIF, ''),
|
|
(
|
|
'Escaping',
|
|
json_enumval_custom_string_pb2.Armor.ARMOR_PAULDRON,
|
|
'p\taul\ndron',
|
|
),
|
|
)
|
|
def testEnumValue(self, enum_value, expected_string):
|
|
msg = json_enumval_custom_string_pb2.Knight(armor=enum_value)
|
|
self.assertEqual(msg.armor, enum_value)
|
|
|
|
json_output = json_format.MessageToJson(msg)
|
|
self.assertEqual(
|
|
json.loads(json_output),
|
|
{'armor': expected_string},
|
|
)
|
|
|
|
# Roundtrip to make sure we can parse it back.
|
|
msg2 = json_enumval_custom_string_pb2.Knight()
|
|
json_format.Parse(json_output, msg2)
|
|
self.assertEqual(msg2.armor, enum_value)
|
|
|
|
def testEnumValueIntOverride(self):
|
|
msg = json_enumval_custom_string_pb2.Knight(
|
|
armor=json_enumval_custom_string_pb2.Armor.ARMOR_GREAT_HELM
|
|
)
|
|
|
|
# Int overrides always win.
|
|
self.assertEqual(
|
|
json.loads(json_format.MessageToJson(msg, use_integers_for_enums=True)),
|
|
{'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
|
|
)
|
|
|
|
def testParseMapFieldWithCustomEnumNameOk(self):
|
|
msg = json_enumval_custom_string_pb2.Knight()
|
|
json_format.Parse(
|
|
'{"armor_map": {"helmet": "gr8 helm", "boots": "sabaton", "chest": 8}}',
|
|
msg,
|
|
)
|
|
self.assertEqual(
|
|
msg.armor_map['helmet'],
|
|
json_enumval_custom_string_pb2.Armor.ARMOR_GREAT_HELM,
|
|
)
|
|
self.assertEqual(
|
|
msg.armor_map['boots'],
|
|
json_enumval_custom_string_pb2.Armor.ARMOR_SABATON,
|
|
)
|
|
self.assertEqual(
|
|
msg.armor_map['chest'],
|
|
json_enumval_custom_string_pb2.Armor.ARMOR_HACHI_MAI_DO,
|
|
)
|
|
|
|
def testGetCustomJsonEnumNames(self):
|
|
enum_type = json_enumval_custom_string_pb2.Armor.DESCRIPTOR
|
|
cache = {}
|
|
names = json_format._GetCustomJsonEnumNames(enum_type, cache)
|
|
self.assertIn('gr8 helm', names)
|
|
self.assertEqual(names['gr8 helm'].name, 'ARMOR_GREAT_HELM')
|
|
self.assertEqual(names['gr8 helm'].number, 1)
|
|
self.assertIn('sabaton', names)
|
|
self.assertEqual(names['sabaton'].number, 7)
|
|
self.assertIn(names['sabaton'].name, ('ARMOR_SABATON', 'ARMOR_SOLLERET'))
|
|
self.assertIn('8', names)
|
|
self.assertEqual(names['8'].name, 'ARMOR_HACHI_MAI_DO')
|
|
self.assertEqual(names['8'].number, 8)
|
|
self.assertNotIn('ARMOR_GORGET', names)
|
|
|
|
# Verify cache is populated and subsequent calls return the cached dict.
|
|
self.assertIn(enum_type, cache)
|
|
self.assertIs(cache[enum_type], names)
|
|
cached_names = json_format._GetCustomJsonEnumNames(enum_type, cache)
|
|
self.assertIs(cached_names, names)
|
|
|
|
def testGetCustomJsonEnumNamesNoCustomOptions(self):
|
|
enum_type = unittest_pb2.ForeignEnum.DESCRIPTOR
|
|
cache = {}
|
|
names = json_format._GetCustomJsonEnumNames(enum_type, cache)
|
|
self.assertEqual(names, {})
|
|
self.assertIn(enum_type, cache)
|
|
self.assertEqual(cache[enum_type], {})
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|