@@ -11,10 +11,13 @@ import 'package:source_gen/source_gen.dart';
11
11
import 'package:source_helper/source_helper.dart' ;
12
12
13
13
import '../default_container.dart' ;
14
+ import '../shared_checkers.dart' ;
14
15
import '../type_helper.dart' ;
16
+ import '../unsupported_type_error.dart' ;
15
17
import '../utils.dart' ;
16
18
import 'config_types.dart' ;
17
19
import 'generic_factory_helper.dart' ;
20
+ import 'to_from_string.dart' ;
18
21
19
22
const _helperLambdaParam = 'value' ;
20
23
@@ -49,12 +52,13 @@ class JsonHelper extends TypeHelper<TypeHelperContextWithConfig> {
49
52
50
53
toJsonArgs.addAll (
51
54
_helperParams (
52
- context.serialize,
53
- _encodeHelper,
54
- interfaceType,
55
- toJson.parameters.where ((element) => element.isRequiredPositional),
56
- toJson,
57
- ),
55
+ context,
56
+ context.serialize,
57
+ _encodeHelper,
58
+ interfaceType,
59
+ toJson.parameters.where ((element) => element.isRequiredPositional),
60
+ toJson,
61
+ isSerializing: true ),
58
62
);
59
63
}
60
64
@@ -108,13 +112,9 @@ class JsonHelper extends TypeHelper<TypeHelperContextWithConfig> {
108
112
109
113
final args = [
110
114
output,
111
- ..._helperParams (
112
- context.deserialize,
113
- _decodeHelper,
114
- targetType,
115
- positionalParams.skip (1 ),
116
- fromJsonCtor,
117
- ),
115
+ ..._helperParams (context, context.deserialize, _decodeHelper,
116
+ targetType, positionalParams.skip (1 ), fromJsonCtor,
117
+ isSerializing: false ),
118
118
];
119
119
120
120
output = args.join (', ' );
@@ -137,13 +137,16 @@ class JsonHelper extends TypeHelper<TypeHelperContextWithConfig> {
137
137
}
138
138
139
139
List <String > _helperParams (
140
+ TypeHelperContextWithConfig context,
140
141
Object ? Function (DartType , String ) execute,
141
- TypeParameterType Function (ParameterElement , Element ) paramMapper,
142
+ TypeParameterTypeWithKeyHelper Function (ParameterElement , Element )
143
+ paramMapper,
142
144
InterfaceType type,
143
145
Iterable <ParameterElement > positionalParams,
144
- Element targetElement,
145
- ) {
146
- final rest = < TypeParameterType > [];
146
+ Element targetElement, {
147
+ required bool isSerializing,
148
+ }) {
149
+ final rest = < TypeParameterTypeWithKeyHelper > [];
147
150
for (var param in positionalParams) {
148
151
rest.add (paramMapper (param, targetElement));
149
152
}
@@ -152,13 +155,19 @@ List<String> _helperParams(
152
155
153
156
for (var helperArg in rest) {
154
157
final typeParamIndex =
155
- type.element.typeParameters.indexOf (helperArg.element);
158
+ type.element.typeParameters.indexOf (helperArg.type. element);
156
159
157
160
// TODO: throw here if `typeParamIndex` is -1 ?
158
161
final typeArg = type.typeArguments[typeParamIndex];
159
162
final body = execute (typeArg, _helperLambdaParam);
160
- if (helperArg.element.declaration.metadata.any ((md) => md.element? .displayName == 'jsonKeyType' )){
161
- final newBody = _helperParams (execute, paramMapper, type, positionalParams, targetElement);
163
+ if (helperArg.type.element.declaration.metadata
164
+ .any ((md) => md.element? .displayName == 'jsonKeyType' )) {
165
+ print ('JsonKeyType' );
166
+ const keyHelper = MapKeyHelper ();
167
+
168
+ final newBody = isSerializing
169
+ ? keyHelper.serialize (typeArg, '' , context)
170
+ : keyHelper.deserialize (typeArg, '' , context, false );
162
171
args.add ('($_helperLambdaParam ) => $newBody /* todo json key */' );
163
172
} else {
164
173
args.add ('($_helperLambdaParam ) => $body ' );
@@ -168,7 +177,7 @@ List<String> _helperParams(
168
177
return args;
169
178
}
170
179
171
- TypeParameterType _decodeHelper (
180
+ TypeParameterTypeWithKeyHelper _decodeHelper (
172
181
ParameterElement param,
173
182
Element targetElement,
174
183
) {
@@ -183,8 +192,11 @@ TypeParameterType _decodeHelper(
183
192
final funcParamType = type.normalParameterTypes.single;
184
193
185
194
if ((funcParamType.isDartCoreObject && funcParamType.isNullableType) ||
186
- funcParamType.isDynamic) {
187
- return funcReturnType as TypeParameterType ;
195
+ funcParamType.isDynamic ||
196
+ funcParamType.isDartCoreString) {
197
+ return TypeParameterTypeWithKeyHelper (
198
+ funcReturnType as TypeParameterType ,
199
+ funcParamType.isDartCoreString);
188
200
}
189
201
}
190
202
}
@@ -199,20 +211,30 @@ TypeParameterType _decodeHelper(
199
211
);
200
212
}
201
213
202
- TypeParameterType _encodeHelper (
214
+ class TypeParameterTypeWithKeyHelper {
215
+ final TypeParameterType type;
216
+ final bool isKey;
217
+
218
+ TypeParameterTypeWithKeyHelper (this .type, this .isKey);
219
+ }
220
+
221
+ TypeParameterTypeWithKeyHelper _encodeHelper (
203
222
ParameterElement param,
204
223
Element targetElement,
205
224
) {
206
225
final type = param.type;
207
226
208
227
if (type is FunctionType &&
209
- (type.returnType.isDartCoreObject || type.returnType.isDynamic) &&
228
+ (type.returnType.isDartCoreObject ||
229
+ type.returnType.isDynamic ||
230
+ type.returnType.isDartCoreString) &&
210
231
type.normalParameterTypes.length == 1 ) {
211
232
final funcParamType = type.normalParameterTypes.single;
212
233
213
234
if (param.name == toJsonForName (funcParamType.element! .name! )) {
214
235
if (funcParamType is TypeParameterType ) {
215
- return funcParamType;
236
+ return TypeParameterTypeWithKeyHelper (
237
+ funcParamType, type.returnType.isDartCoreString);
216
238
}
217
239
}
218
240
}
@@ -295,3 +317,119 @@ ClassConfig? _annotation(ClassConfig config, InterfaceType source) {
295
317
MethodElement ? _toJsonMethod (DartType type) => type.typeImplementations
296
318
.map ((dt) => dt is InterfaceType ? dt.getMethod ('toJson' ) : null )
297
319
.firstWhereOrNull ((me) => me != null );
320
+
321
+ class MapKeyHelper extends TypeHelper <TypeHelperContextWithConfig > {
322
+ const MapKeyHelper ();
323
+
324
+ @override
325
+ String ? serialize (
326
+ DartType targetType,
327
+ String expression,
328
+ TypeHelperContextWithConfig context,
329
+ ) {
330
+ final keyType = targetType;
331
+
332
+ _checkSafeKeyType (expression, keyType);
333
+
334
+ final subKeyValue =
335
+ _forType (keyType)? .serialize (keyType, _helperLambdaParam, false ) ??
336
+ context.serialize (keyType, _helperLambdaParam);
337
+
338
+ if (_helperLambdaParam == subKeyValue) {
339
+ return expression;
340
+ }
341
+
342
+ return '$subKeyValue ' ;
343
+ }
344
+
345
+ @override
346
+ String ? deserialize (
347
+ DartType targetType,
348
+ String expression,
349
+ TypeHelperContextWithConfig context,
350
+ bool defaultProvided,
351
+ ) {
352
+ final keyArg = targetType;
353
+
354
+ _checkSafeKeyType (expression, keyArg);
355
+
356
+ final isKeyStringable = _isKeyStringable (keyArg);
357
+ if (! isKeyStringable) {
358
+ throw UnsupportedTypeError (
359
+ keyArg,
360
+ expression,
361
+ 'Map keys must be one of: ${_allowedTypeNames .join (', ' )}.' ,
362
+ );
363
+ }
364
+
365
+ String keyUsage;
366
+ if (keyArg.isEnum) {
367
+ keyUsage = context.deserialize (keyArg, _helperLambdaParam).toString ();
368
+ } else if (context.config.anyMap &&
369
+ ! (keyArg.isDartCoreObject || keyArg.isDynamic)) {
370
+ keyUsage = '$_helperLambdaParam as String' ;
371
+ } else if (context.config.anyMap &&
372
+ keyArg.isDartCoreObject &&
373
+ ! keyArg.isNullableType) {
374
+ keyUsage = '$_helperLambdaParam as Object' ;
375
+ } else {
376
+ print ('Key is $keyArg ' );
377
+ keyUsage = '$_helperLambdaParam as String' ;
378
+ }
379
+
380
+ final toFromString = _forType (keyArg);
381
+ if (toFromString != null ) {
382
+ keyUsage = toFromString.deserialize (keyArg, keyUsage, false , true )! ;
383
+ }
384
+
385
+ return keyUsage;
386
+ }
387
+ }
388
+
389
+ final _intString = ToFromStringHelper ('int.parse' , 'toString()' , 'int' );
390
+
391
+ /// [ToFromStringHelper] instances representing non-String types that can
392
+ /// be used as [Map] keys.
393
+ final _instances = [
394
+ bigIntString,
395
+ dateTimeString,
396
+ _intString,
397
+ uriString,
398
+ ];
399
+
400
+ ToFromStringHelper ? _forType (DartType type) =>
401
+ _instances.singleWhereOrNull ((i) => i.matches (type));
402
+
403
+ /// Returns `true` if [keyType] can be automatically converted to/from String –
404
+ /// and is therefor usable as a key in a [Map] .
405
+ bool _isKeyStringable (DartType keyType) =>
406
+ keyType.isEnum || _instances.any ((inst) => inst.matches (keyType));
407
+
408
+ void _checkSafeKeyType (String expression, DartType keyArg) {
409
+ // We're not going to handle converting key types at the moment
410
+ // So the only safe types for key are dynamic/Object/String/enum
411
+ if (keyArg.isDynamic ||
412
+ (! keyArg.isNullableType &&
413
+ (keyArg.isDartCoreObject ||
414
+ coreStringTypeChecker.isExactlyType (keyArg) ||
415
+ _isKeyStringable (keyArg)))) {
416
+ return ;
417
+ }
418
+
419
+ throw UnsupportedTypeError (
420
+ keyArg,
421
+ expression,
422
+ 'Map keys must be one of: ${_allowedTypeNames .join (', ' )}.' ,
423
+ );
424
+ }
425
+
426
+ /// The names of types that can be used as [Map] keys.
427
+ ///
428
+ /// Used in [_checkSafeKeyType] to provide a helpful error with unsupported
429
+ /// types.
430
+ Iterable <String > get _allowedTypeNames => const [
431
+ 'Object' ,
432
+ 'dynamic' ,
433
+ 'enum' ,
434
+ 'String' ,
435
+ ].followedBy (_instances.map ((i) => i.coreTypeName));
0 commit comments