@@ -8,6 +8,7 @@ import '../constants.dart';
8
8
import '../shared_checkers.dart' ;
9
9
import '../type_helper.dart' ;
10
10
import '../utils.dart' ;
11
+ import 'to_from_string.dart' ;
11
12
12
13
const _keyParam = 'k' ;
13
14
@@ -29,7 +30,9 @@ class MapHelper extends TypeHelper<TypeHelperContextWithConfig> {
29
30
_checkSafeKeyType (expression, keyType);
30
31
31
32
final subFieldValue = context.serialize (valueType, closureArg);
32
- final subKeyValue = context.serialize (keyType, _keyParam);
33
+ final subKeyValue =
34
+ _forType (keyType)? .serialize (keyType, _keyParam, false ) ??
35
+ context.serialize (keyType, _keyParam);
33
36
34
37
if (closureArg == subFieldValue && _keyParam == subKeyValue) {
35
38
return expression;
@@ -56,9 +59,9 @@ class MapHelper extends TypeHelper<TypeHelperContextWithConfig> {
56
59
_checkSafeKeyType (expression, keyArg);
57
60
58
61
final valueArgIsAny = _isObjectOrDynamic (valueArg);
59
- final isEnumKey = isEnum (keyArg);
62
+ final isKeyStringable = _isKeyStringable (keyArg);
60
63
61
- if (! isEnumKey ) {
64
+ if (! isKeyStringable ) {
62
65
if (valueArgIsAny) {
63
66
if (context.config.anyMap) {
64
67
if (_isObjectOrDynamic (keyArg)) {
@@ -90,30 +93,65 @@ class MapHelper extends TypeHelper<TypeHelperContextWithConfig> {
90
93
context.config.anyMap ? 'as Map' : 'as Map<String, dynamic>' ;
91
94
92
95
String keyUsage;
93
- if (isEnumKey ) {
96
+ if (isEnum (keyArg) ) {
94
97
keyUsage = context.deserialize (keyArg, _keyParam).toString ();
95
98
} else if (context.config.anyMap && ! _isObjectOrDynamic (keyArg)) {
96
99
keyUsage = '$_keyParam as String' ;
97
100
} else {
98
101
keyUsage = _keyParam;
99
102
}
100
103
104
+ final toFromString = _forType (keyArg);
105
+ if (toFromString != null ) {
106
+ keyUsage = toFromString.deserialize (keyArg, keyUsage, false , true );
107
+ }
108
+
101
109
return '($expression $mapCast )$optionalQuestion .map('
102
110
'($_keyParam , $closureArg ) => MapEntry($keyUsage , $itemSubVal ),)' ;
103
111
}
104
112
}
105
113
114
+ final _intString = ToFromStringHelper ('int.parse' , 'toString()' , 'int' );
115
+
116
+ /// [ToFromStringHelper] instances representing non-String types that can
117
+ /// be used as [Map] keys.
118
+ final _instances = [
119
+ bigIntString,
120
+ dateTimeString,
121
+ _intString,
122
+ uriString,
123
+ ];
124
+
125
+ ToFromStringHelper _forType (DartType type) =>
126
+ _instances.singleWhere ((i) => i.matches (type), orElse: () => null );
127
+
106
128
bool _isObjectOrDynamic (DartType type) => type.isObject || type.isDynamic;
107
129
130
+ /// Returns `true` if [keyType] can be automatically converted to/from String –
131
+ /// and is therefor usable as a key in a [Map] .
132
+ bool _isKeyStringable (DartType keyType) =>
133
+ isEnum (keyType) || _instances.any ((inst) => inst.matches (keyType));
134
+
108
135
void _checkSafeKeyType (String expression, DartType keyArg) {
109
136
// We're not going to handle converting key types at the moment
110
137
// So the only safe types for key are dynamic/Object/String/enum
111
138
final safeKey = _isObjectOrDynamic (keyArg) ||
112
139
coreStringTypeChecker.isExactlyType (keyArg) ||
113
- isEnum (keyArg);
140
+ _isKeyStringable (keyArg);
114
141
115
142
if (! safeKey) {
116
143
throw UnsupportedTypeError (keyArg, expression,
117
- 'Map keys must be of type `String`, enum, `Object` or `dynamic` .' );
144
+ 'Map keys must be one of: ${ _allowedTypeNames . join ( ', ' )} .' );
118
145
}
119
146
}
147
+
148
+ /// The names of types that can be used as [Map] keys.
149
+ ///
150
+ /// Used in [_checkSafeKeyType] to provide a helpful error with unsupported
151
+ /// types.
152
+ Iterable <String > get _allowedTypeNames => const [
153
+ 'Object' ,
154
+ 'dynamic' ,
155
+ 'enum' ,
156
+ 'String' ,
157
+ ].followedBy (_instances.map ((i) => i.coreTypeName));
0 commit comments