Account for double.infinity and double.nan in json serializers (#652)
Port of cl/398287072
diff --git a/protobuf/lib/protobuf.dart b/protobuf/lib/protobuf.dart
index f60d5ee..97e9044 100644
--- a/protobuf/lib/protobuf.dart
+++ b/protobuf/lib/protobuf.dart
@@ -21,6 +21,7 @@
part 'src/protobuf/coded_buffer.dart';
part 'src/protobuf/coded_buffer_reader.dart';
part 'src/protobuf/coded_buffer_writer.dart';
+part 'src/protobuf/consts.dart';
part 'src/protobuf/builder_info.dart';
part 'src/protobuf/event_plugin.dart';
part 'src/protobuf/exceptions.dart';
diff --git a/protobuf/lib/src/protobuf/consts.dart b/protobuf/lib/src/protobuf/consts.dart
new file mode 100644
index 0000000..a4fcd16
--- /dev/null
+++ b/protobuf/lib/src/protobuf/consts.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of protobuf;
+
+/// Constant string value of double.infinity.toString() and the infinity value
+/// recognized by double.parse(..).
+const infinity = 'Infinity';
+
+/// Constant string value of double.negativeInfinity.toString() and the negative
+/// infinity value recognized by double.parse(..).
+const negativeInfinity = '-Infinity';
+
+/// Constant string value of double.nan.toString() and the NaN (not a number)
+/// value recognized by double.parse(..).
+const nan = 'NaN';
diff --git a/protobuf/lib/src/protobuf/json.dart b/protobuf/lib/src/protobuf/json.dart
index f42d12e..011323f 100644
--- a/protobuf/lib/src/protobuf/json.dart
+++ b/protobuf/lib/src/protobuf/json.dart
@@ -15,14 +15,25 @@
switch (baseType) {
case PbFieldType._BOOL_BIT:
case PbFieldType._STRING_BIT:
- case PbFieldType._FLOAT_BIT:
- case PbFieldType._DOUBLE_BIT:
case PbFieldType._INT32_BIT:
case PbFieldType._SINT32_BIT:
case PbFieldType._UINT32_BIT:
case PbFieldType._FIXED32_BIT:
case PbFieldType._SFIXED32_BIT:
return fieldValue;
+ case PbFieldType._FLOAT_BIT:
+ case PbFieldType._DOUBLE_BIT:
+ final value = fieldValue as double;
+ if (value.isNaN) {
+ return nan;
+ }
+ if (value.isInfinite) {
+ return value.isNegative ? negativeInfinity : infinity;
+ }
+ if (fieldValue.toInt() == fieldValue) {
+ return fieldValue.toInt();
+ }
+ return value;
case PbFieldType._BYTES_BIT:
// Encode 'bytes' as a base64-encoded string.
return base64Encode(fieldValue as List<int>);
diff --git a/protobuf/lib/src/protobuf/proto3_json.dart b/protobuf/lib/src/protobuf/proto3_json.dart
index ade908b..ac2fb63 100644
--- a/protobuf/lib/src/protobuf/proto3_json.dart
+++ b/protobuf/lib/src/protobuf/proto3_json.dart
@@ -61,13 +61,11 @@
case PbFieldType._FLOAT_BIT:
case PbFieldType._DOUBLE_BIT:
double value = fieldValue;
- if (value.isNaN) return 'NaN';
+ if (value.isNaN) {
+ return nan;
+ }
if (value.isInfinite) {
- if (value.isNegative) {
- return '-Infinity';
- } else {
- return 'Infinity';
- }
+ return value.isNegative ? negativeInfinity : infinity;
}
return value;
case PbFieldType._UINT64_BIT:
diff --git a/protobuf/test/json_test.dart b/protobuf/test/json_test.dart
index 6291ad8..bf72b2b 100644
--- a/protobuf/test/json_test.dart
+++ b/protobuf/test/json_test.dart
@@ -105,7 +105,7 @@
expect(decoded.int64, value);
});
- test('tesFrozentInt64JsonEncoding', () {
+ test('testFrozentInt64JsonEncoding', () {
final value = Int64.parseInt('1234567890123456789');
final frozen = T()
..int64 = value
diff --git a/protoc_plugin/test/json_test.dart b/protoc_plugin/test/json_test.dart
index 15c9326..8e5f6f1 100755
--- a/protoc_plugin/test/json_test.dart
+++ b/protoc_plugin/test/json_test.dart
@@ -14,19 +14,19 @@
void main() {
final testAllJsonTypes = '{"1":101,"2":"102","3":103,"4":"104",'
- '"5":105,"6":"106","7":107,"8":"108","9":109,"10":"110","11":111.0,'
- '"12":112.0,"13":true,"14":"115","15":"MTE2","16":{"17":117},'
+ '"5":105,"6":"106","7":107,"8":"108","9":109,"10":"110","11":111,'
+ '"12":112,"13":true,"14":"115","15":"MTE2","16":{"17":117},'
'"18":{"1":118},"19":{"1":119},"20":{"1":120},"21":3,"22":6,"23":9,'
'"24":"124","25":"125","31":[201,301],"32":["202","302"],'
'"33":[203,303],"34":["204","304"],"35":[205,305],"36":["206","306"],'
'"37":[207,307],"38":["208","308"],"39":[209,309],"40":["210","310"],'
- '"41":[211.0,311.0],"42":[212.0,312.0],"43":[true,false],'
+ '"41":[211,311],"42":[212,312],"43":[true,false],'
'"44":["215","315"],"45":["MjE2","MzE2"],"46":[{"47":217},{"47":317}],'
'"48":[{"1":218},{"1":318}],"49":[{"1":219},{"1":319}],'
'"50":[{"1":220},{"1":320}],"51":[2,3],"52":[5,6],"53":[8,9],'
'"54":["224","324"],"55":["225","325"],"61":401,"62":"402","63":403,'
'"64":"404","65":405,"66":"406","67":407,"68":"408","69":409,'
- '"70":"410","71":411.0,"72":412.0,"73":false,"74":"415","75":"NDE2",'
+ '"70":"410","71":411,"72":412,"73":false,"74":"415","75":"NDE2",'
'"81":1,"82":4,"83":7,"84":"424","85":"425"}';
// Checks that message once serialized to JSON
@@ -128,6 +128,37 @@
expect(parsed, expected);
});
+ group('testConvertDouble', () {
+ test('WithDecimal', () {
+ final json = '{"12":1.2}';
+ TestAllTypes proto = TestAllTypes()..optionalDouble = 1.2;
+ expect(TestAllTypes.fromJson(json), proto);
+ expect(proto.writeToJson(), json);
+ });
+
+ test('WholeNumber', () {
+ final json = '{"12":5}';
+ TestAllTypes proto = TestAllTypes()..optionalDouble = 5.0;
+ expect(TestAllTypes.fromJson(json), proto);
+ expect(proto.writeToJson(), json);
+ });
+
+ test('Infinity', () {
+ final json = '{"12":"Infinity"}';
+ TestAllTypes proto = TestAllTypes()..optionalDouble = double.infinity;
+ expect(TestAllTypes.fromJson(json), proto);
+ expect(proto.writeToJson(), json);
+ });
+
+ test('NegativeInfinity', () {
+ final json = '{"12":"-Infinity"}';
+ TestAllTypes proto = TestAllTypes()
+ ..optionalDouble = double.negativeInfinity;
+ expect(TestAllTypes.fromJson(json), proto);
+ expect(proto.writeToJson(), json);
+ });
+ });
+
test('testParseUnsignedLegacy', () {
var parsed = TestAllTypes.fromJson(
'{"4":"-1152921500311945216","8":"-1152921500311945215"}');
diff --git a/protoc_plugin/test/proto3_json_test.dart b/protoc_plugin/test/proto3_json_test.dart
index 791ae39..0856ee4 100644
--- a/protoc_plugin/test/proto3_json_test.dart
+++ b/protoc_plugin/test/proto3_json_test.dart
@@ -1258,4 +1258,35 @@
expectSecondSet(Foo()..mergeFromProto3Json({'second': 1}));
expectOneofNotSet(Foo()..mergeFromProto3Json({}));
});
+
+ group('Convert Double', () {
+ test('With Decimal', () {
+ final json = {'optionalDouble': 1.2};
+ TestAllTypes proto = TestAllTypes()..optionalDouble = 1.2;
+ expect(TestAllTypes()..mergeFromProto3Json(json), proto);
+ expect(proto.toProto3Json(), json);
+ });
+
+ test('Whole Number', () {
+ final json = {'optionalDouble': 5};
+ TestAllTypes proto = TestAllTypes()..optionalDouble = 5.0;
+ expect(TestAllTypes()..mergeFromProto3Json(json), proto);
+ expect(proto.toProto3Json(), json);
+ });
+
+ test('Infinity', () {
+ final json = {'optionalDouble': 'Infinity'};
+ TestAllTypes proto = TestAllTypes()..optionalDouble = double.infinity;
+ expect(TestAllTypes()..mergeFromProto3Json(json), proto);
+ expect(proto.toProto3Json(), json);
+ });
+
+ test('Negative Infinity', () {
+ final json = {'optionalDouble': '-Infinity'};
+ TestAllTypes proto = TestAllTypes()
+ ..optionalDouble = double.negativeInfinity;
+ expect(TestAllTypes()..mergeFromProto3Json(json), proto);
+ expect(proto.toProto3Json(), json);
+ });
+ });
}