Skip to content

Commit 34e2a7f

Browse files
Fix listDeserializer casting issue in Optional
The `listDeserializer` function was incorrectly casting the input `data` to `List<T>`. When deserializing JSON, the input is typically `List<dynamic>`, which cannot be cast to `List<T>` (unless T is dynamic). This caused a runtime `TypeError`. This change modifies `listDeserializer` and `listSerializer` to cast the input to `List` (which is `List<dynamic>`) and rely on the `map` function to handle the type conversion, ensuring correct runtime behavior for JSON lists. Added regression tests to verify the fix and ensure type safety is maintained.
1 parent 345e14f commit 34e2a7f

2 files changed

Lines changed: 66 additions & 2 deletions

File tree

packages/firebase_data_connect/firebase_data_connect/lib/src/optional.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ DynamicDeserializer<List<T>> listDeserializer<T>(
120120
DynamicDeserializer<T> deserializer,
121121
) {
122122
return (dynamic data) =>
123-
(data as List<T>).map((e) => deserializer(e)).toList();
123+
(data as List).map((e) => deserializer(e)).toList();
124124
}
125125

126126
DynamicSerializer<List<T>> listSerializer<T>(DynamicSerializer<T> serializer) {
127-
return (dynamic data) => (data as List<T>).map((e) => serializer(e)).toList();
127+
return (dynamic data) => (data as List).map((e) => serializer(e as T)).toList();
128128
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import 'package:firebase_data_connect/firebase_data_connect.dart';
16+
import 'package:firebase_data_connect/src/common/common_library.dart';
17+
import 'package:flutter_test/flutter_test.dart';
18+
19+
void main() {
20+
group('Optional Regression Tests', () {
21+
test('listDeserializer should handle List<dynamic> input', () {
22+
final stringDeserializer = (dynamic json) => json as String;
23+
final deserializer = listDeserializer(stringDeserializer);
24+
25+
// Simulating JSON decode which produces List<dynamic>
26+
final List<dynamic> jsonList = ['a', 'b'];
27+
28+
final result = deserializer(jsonList);
29+
expect(result, isA<List<String>>());
30+
expect(result, equals(['a', 'b']));
31+
});
32+
33+
test('listSerializer should handle List<dynamic> input if elements are correct type', () {
34+
final stringSerializer = (String value) => value;
35+
final serializer = listSerializer(stringSerializer);
36+
37+
// List<dynamic> but contains Strings
38+
final List<dynamic> list = ['a', 'b'];
39+
40+
// We need to cast it to List<String> to pass type check of `DynamicSerializer<List<T>>`
41+
// which expects `List<T>`.
42+
// Wait, `DynamicSerializer` is defined as `typedef DynamicSerializer<Variables> = dynamic Function(Variables vars);`
43+
// So `listSerializer<String>` returns `DynamicSerializer<List<String>>` i.e. `dynamic Function(List<String> vars)`.
44+
// Thus we cannot pass `List<dynamic>` to it directly in a statically typed language if strict checks are on.
45+
// But we can cast it to dynamic first to bypass static check, or use `Function.apply`.
46+
47+
// However, the internal implementation of `listSerializer` casts `data` to `List`.
48+
// So if we pass it as dynamic, it should work.
49+
50+
final result = (serializer as Function)(list);
51+
expect(result, equals(['a', 'b']));
52+
});
53+
54+
test('listSerializer should fail if elements are incorrect type', () {
55+
final stringSerializer = (String value) => value;
56+
final serializer = listSerializer(stringSerializer);
57+
58+
// List<dynamic> contains int
59+
final List<dynamic> list = [1, 2];
60+
61+
expect(() => (serializer as Function)(list), throwsA(isA<TypeError>()));
62+
});
63+
});
64+
}

0 commit comments

Comments
 (0)