Skip to content

Commit 58a6031

Browse files
committed
get basics working
1 parent 721b64e commit 58a6031

File tree

3 files changed

+169
-29
lines changed

3 files changed

+169
-29
lines changed

json_serializable/example/example.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ class Person2<@jsonKeyType K, V> {
4545
factory Person2.fromJson(Map<String, dynamic> json,
4646
K Function(Object?) fromJsonK, V Function(Object?) fromJsonV) =>
4747
Person2(id: fromJsonK(json), name: fromJsonV(json));
48-
Object toJson(Object? Function(K) toJsonK, Object? Function(V) toJsonV) => {
48+
Map<String, dynamic> toJson(
49+
String Function(K) toJsonK, Object? Function(V) toJsonV) =>
50+
{
4951
toJsonK(id): toJsonV(name),
5052
};
5153
}

json_serializable/example/example.g.dart

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

json_serializable/lib/src/type_helpers/json_helper.dart

Lines changed: 164 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@ import 'package:source_gen/source_gen.dart';
1111
import 'package:source_helper/source_helper.dart';
1212

1313
import '../default_container.dart';
14+
import '../shared_checkers.dart';
1415
import '../type_helper.dart';
16+
import '../unsupported_type_error.dart';
1517
import '../utils.dart';
1618
import 'config_types.dart';
1719
import 'generic_factory_helper.dart';
20+
import 'to_from_string.dart';
1821

1922
const _helperLambdaParam = 'value';
2023

@@ -49,12 +52,13 @@ class JsonHelper extends TypeHelper<TypeHelperContextWithConfig> {
4952

5053
toJsonArgs.addAll(
5154
_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),
5862
);
5963
}
6064

@@ -108,13 +112,9 @@ class JsonHelper extends TypeHelper<TypeHelperContextWithConfig> {
108112

109113
final args = [
110114
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),
118118
];
119119

120120
output = args.join(', ');
@@ -137,13 +137,16 @@ class JsonHelper extends TypeHelper<TypeHelperContextWithConfig> {
137137
}
138138

139139
List<String> _helperParams(
140+
TypeHelperContextWithConfig context,
140141
Object? Function(DartType, String) execute,
141-
TypeParameterType Function(ParameterElement, Element) paramMapper,
142+
TypeParameterTypeWithKeyHelper Function(ParameterElement, Element)
143+
paramMapper,
142144
InterfaceType type,
143145
Iterable<ParameterElement> positionalParams,
144-
Element targetElement,
145-
) {
146-
final rest = <TypeParameterType>[];
146+
Element targetElement, {
147+
required bool isSerializing,
148+
}) {
149+
final rest = <TypeParameterTypeWithKeyHelper>[];
147150
for (var param in positionalParams) {
148151
rest.add(paramMapper(param, targetElement));
149152
}
@@ -152,13 +155,19 @@ List<String> _helperParams(
152155

153156
for (var helperArg in rest) {
154157
final typeParamIndex =
155-
type.element.typeParameters.indexOf(helperArg.element);
158+
type.element.typeParameters.indexOf(helperArg.type.element);
156159

157160
// TODO: throw here if `typeParamIndex` is -1 ?
158161
final typeArg = type.typeArguments[typeParamIndex];
159162
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);
162171
args.add('($_helperLambdaParam) => $newBody /* todo json key */');
163172
} else {
164173
args.add('($_helperLambdaParam) => $body');
@@ -168,7 +177,7 @@ List<String> _helperParams(
168177
return args;
169178
}
170179

171-
TypeParameterType _decodeHelper(
180+
TypeParameterTypeWithKeyHelper _decodeHelper(
172181
ParameterElement param,
173182
Element targetElement,
174183
) {
@@ -183,8 +192,11 @@ TypeParameterType _decodeHelper(
183192
final funcParamType = type.normalParameterTypes.single;
184193

185194
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);
188200
}
189201
}
190202
}
@@ -199,20 +211,30 @@ TypeParameterType _decodeHelper(
199211
);
200212
}
201213

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(
203222
ParameterElement param,
204223
Element targetElement,
205224
) {
206225
final type = param.type;
207226

208227
if (type is FunctionType &&
209-
(type.returnType.isDartCoreObject || type.returnType.isDynamic) &&
228+
(type.returnType.isDartCoreObject ||
229+
type.returnType.isDynamic ||
230+
type.returnType.isDartCoreString) &&
210231
type.normalParameterTypes.length == 1) {
211232
final funcParamType = type.normalParameterTypes.single;
212233

213234
if (param.name == toJsonForName(funcParamType.element!.name!)) {
214235
if (funcParamType is TypeParameterType) {
215-
return funcParamType;
236+
return TypeParameterTypeWithKeyHelper(
237+
funcParamType, type.returnType.isDartCoreString);
216238
}
217239
}
218240
}
@@ -295,3 +317,119 @@ ClassConfig? _annotation(ClassConfig config, InterfaceType source) {
295317
MethodElement? _toJsonMethod(DartType type) => type.typeImplementations
296318
.map((dt) => dt is InterfaceType ? dt.getMethod('toJson') : null)
297319
.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

Comments
 (0)