From 1c2555e430757d3b5e6248e216630c2530a42809 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Fri, 14 Aug 2026 15:31:16 +0200 Subject: [PATCH 1/2] fix(realtime): parse whole-number doubles in toInt transformer --- .../realtime_client/lib/src/transformers.dart | 21 ++++++++++++++++++- .../test/transformers_test.dart | 8 +++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/realtime_client/lib/src/transformers.dart b/packages/realtime_client/lib/src/transformers.dart index f19e9c400..54c22e1dc 100644 --- a/packages/realtime_client/lib/src/transformers.dart +++ b/packages/realtime_client/lib/src/transformers.dart @@ -233,10 +233,29 @@ int? toInt(dynamic value) { if (value is int) { return value; } + if (value is double) { + return _wholeDoubleToInt(value); + } if (value == null) { return null; } - return int.tryParse(value.toString()); + final stringValue = value.toString(); + final parsedInt = int.tryParse(stringValue); + if (parsedInt != null) { + return parsedInt; + } + final parsedDouble = double.tryParse(stringValue); + if (parsedDouble == null) { + return null; + } + return _wholeDoubleToInt(parsedDouble); +} + +int? _wholeDoubleToInt(double value) { + if (!value.isFinite || value.truncateToDouble() != value) { + return null; + } + return value.toInt(); } @internal diff --git a/packages/realtime_client/test/transformers_test.dart b/packages/realtime_client/test/transformers_test.dart index 38e752c96..4d93a518e 100644 --- a/packages/realtime_client/test/transformers_test.dart +++ b/packages/realtime_client/test/transformers_test.dart @@ -23,6 +23,14 @@ void main() { test('transformers toInt', () { expect(toInt(10), equals(10)); expect(toInt('10'), equals(10)); + expect(toInt(10.0), equals(10)); + expect(toInt('10.0'), equals(10)); + expect(toInt('1e3'), equals(1000)); + expect(toInt(10.5), isNull); + expect(toInt('10.5'), isNull); + expect(toInt(double.nan), isNull); + expect(toInt(double.infinity), isNull); + expect(toInt('NaN'), isNull); expect(toInt(null), isNull); expect(toInt(''), isNull); expect(toInt('not a number'), isNull); From 3496e44238cbf653add8fd17877e597e40d273cd Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Fri, 14 Aug 2026 16:45:59 +0200 Subject: [PATCH 2/2] fix(realtime): keep exact integer values when parsing numeric strings --- .../realtime_client/lib/src/transformers.dart | 19 ++++++++++++++++--- .../test/transformers_test.dart | 10 +++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/realtime_client/lib/src/transformers.dart b/packages/realtime_client/lib/src/transformers.dart index 54c22e1dc..f2a8dc099 100644 --- a/packages/realtime_client/lib/src/transformers.dart +++ b/packages/realtime_client/lib/src/transformers.dart @@ -244,17 +244,30 @@ int? toInt(dynamic value) { if (parsedInt != null) { return parsedInt; } - final parsedDouble = double.tryParse(stringValue); - if (parsedDouble == null) { + final match = _integerWithZeroFraction.firstMatch(stringValue); + if (match == null) { return null; } - return _wholeDoubleToInt(parsedDouble); + return int.tryParse(match.group(1)!); } +/// Matches an integer with a decimal fraction of only zeros, such as `10.0` +/// or `-3.000`. Parsing the integer part directly keeps values above 2^53 +/// exact, which a round trip through [double] would not. +final _integerWithZeroFraction = RegExp(r'^([+-]?\d+)\.0+$'); + +/// The lowest and highest [double] values enclosing the native 64-bit +/// integer range, -2^63 and 2^63. Both are exactly representable as doubles. +const _minIntAsDouble = -9223372036854775808.0; +const _maxIntExclusiveAsDouble = 9223372036854775808.0; + int? _wholeDoubleToInt(double value) { if (!value.isFinite || value.truncateToDouble() != value) { return null; } + if (value < _minIntAsDouble || value >= _maxIntExclusiveAsDouble) { + return null; + } return value.toInt(); } diff --git a/packages/realtime_client/test/transformers_test.dart b/packages/realtime_client/test/transformers_test.dart index 4d93a518e..829bc1596 100644 --- a/packages/realtime_client/test/transformers_test.dart +++ b/packages/realtime_client/test/transformers_test.dart @@ -25,11 +25,19 @@ void main() { expect(toInt('10'), equals(10)); expect(toInt(10.0), equals(10)); expect(toInt('10.0'), equals(10)); - expect(toInt('1e3'), equals(1000)); + expect(toInt('-3.000'), equals(-3)); + // Above 2^53, where a round trip through double would lose precision. + expect(toInt('9007199254740993.0'), equals(9007199254740993)); + // Outside the 64-bit integer range. + expect(toInt('9223372036854775808.0'), isNull); + expect(toInt(1e19), isNull); + expect(toInt(-1e19), isNull); + expect(toInt('1e3'), isNull); expect(toInt(10.5), isNull); expect(toInt('10.5'), isNull); expect(toInt(double.nan), isNull); expect(toInt(double.infinity), isNull); + expect(toInt(double.negativeInfinity), isNull); expect(toInt('NaN'), isNull); expect(toInt(null), isNull); expect(toInt(''), isNull);