2018-02-28 00:59:43 +00:00
|
|
|
part of angel_serialize_generator;
|
|
|
|
|
|
|
|
class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
|
|
|
|
final bool autoSnakeCaseNames;
|
|
|
|
|
|
|
|
const SerializerGenerator({this.autoSnakeCaseNames: true});
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<String> generateForAnnotatedElement(
|
|
|
|
Element element, ConstantReader annotation, BuildStep buildStep) async {
|
|
|
|
if (element.kind != ElementKind.CLASS)
|
|
|
|
throw 'Only classes can be annotated with a @Serializable() annotation.';
|
|
|
|
|
|
|
|
var ctx = await buildContext(element, annotation, buildStep,
|
|
|
|
await buildStep.resolver, true, autoSnakeCaseNames != false);
|
|
|
|
|
|
|
|
var serializers = annotation.peek('serializers')?.listValue ?? [];
|
|
|
|
|
|
|
|
if (serializers.isEmpty) return null;
|
|
|
|
|
|
|
|
// Check if any serializer is recognized
|
|
|
|
if (!serializers.any((s) => Serializers.all.contains(s.toIntValue()))) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
var lib = new File((b) {
|
2018-02-28 01:10:57 +00:00
|
|
|
generateClass(serializers.map((s) => s.toIntValue()).toList(), ctx, b);
|
2018-02-28 00:59:43 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
var buf = lib.accept(new DartEmitter());
|
|
|
|
return buf.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generate a serializer class.
|
2018-02-28 01:10:57 +00:00
|
|
|
void generateClass(
|
|
|
|
List<int> serializers, BuildContext ctx, FileBuilder file) {
|
2018-02-28 00:59:43 +00:00
|
|
|
file.body.add(new Class((clazz) {
|
|
|
|
clazz
|
|
|
|
..name = '${ctx.modelClassNameRecase.pascalCase}Serializer'
|
|
|
|
..abstract = true;
|
2018-02-28 01:10:57 +00:00
|
|
|
|
|
|
|
if (serializers.contains(Serializers.map)) {
|
2018-02-28 02:10:43 +00:00
|
|
|
generateFromMapMethod(clazz, ctx, file);
|
2018-02-28 01:10:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (serializers.contains(Serializers.map) ||
|
|
|
|
serializers.contains(Serializers.json)) {
|
|
|
|
generateToMapMethod(clazz, ctx, file);
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
void generateToMapMethod(
|
|
|
|
ClassBuilder clazz, BuildContext ctx, FileBuilder file) {
|
|
|
|
clazz.methods.add(new Method((method) {
|
|
|
|
method
|
2018-02-28 01:41:19 +00:00
|
|
|
..static = true
|
2018-02-28 01:10:57 +00:00
|
|
|
..name = 'toMap'
|
2018-02-28 01:41:19 +00:00
|
|
|
..returns = new Reference('Map<String, dynamic>')
|
|
|
|
..requiredParameters.add(new Parameter((b) {
|
|
|
|
b
|
|
|
|
..name = 'model'
|
|
|
|
..type = ctx.modelClassType;
|
|
|
|
}));
|
2018-02-28 01:10:57 +00:00
|
|
|
|
|
|
|
var buf = new StringBuffer('return {');
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
// Add named parameters
|
|
|
|
for (var field in ctx.fields) {
|
|
|
|
// Skip excluded fields
|
2018-03-02 18:38:41 +00:00
|
|
|
if (ctx.excluded[field.name]?.canSerialize == false) continue;
|
2018-02-28 01:10:57 +00:00
|
|
|
|
|
|
|
var alias = ctx.resolveFieldName(field.name);
|
|
|
|
|
|
|
|
if (i++ > 0) buf.write(', ');
|
|
|
|
|
2018-02-28 01:41:19 +00:00
|
|
|
String serializedRepresentation = 'model.${field.name}';
|
2018-02-28 01:10:57 +00:00
|
|
|
|
2018-02-28 01:18:48 +00:00
|
|
|
// Serialize dates
|
2018-02-28 01:10:57 +00:00
|
|
|
if (dateTimeTypeChecker.isAssignableFromType(field.type))
|
2018-02-28 02:10:43 +00:00
|
|
|
serializedRepresentation = 'model.${field.name}?.toIso8601String()';
|
2018-02-28 01:10:57 +00:00
|
|
|
|
2018-02-28 01:18:48 +00:00
|
|
|
// Serialize model classes via `XSerializer.toMap`
|
|
|
|
else if (isModelClass(field.type)) {
|
2018-02-28 01:20:42 +00:00
|
|
|
var rc = new ReCase(field.type.name);
|
2018-02-28 01:41:19 +00:00
|
|
|
serializedRepresentation =
|
|
|
|
'${rc.pascalCase}Serializer.toMap(model.${field.name})';
|
2018-02-28 02:10:43 +00:00
|
|
|
} else if (field.type is InterfaceType) {
|
2018-02-28 01:41:19 +00:00
|
|
|
var t = field.type as InterfaceType;
|
|
|
|
|
|
|
|
if (t.name == 'List' && t.typeArguments.length == 1) {
|
|
|
|
var rc = new ReCase(t.typeArguments[0].name);
|
2018-02-28 02:10:43 +00:00
|
|
|
serializedRepresentation = 'model.${field.name}?.map(${rc
|
|
|
|
.pascalCase}Serializer.toMap)?.toList()';
|
|
|
|
} else if (isMapToModelType(t)) {
|
2018-02-28 01:43:43 +00:00
|
|
|
var rc = new ReCase(t.typeArguments[1].name);
|
2018-02-28 02:10:43 +00:00
|
|
|
serializedRepresentation =
|
|
|
|
'''model.${field.name}.keys?.fold({}, (map, key) {
|
|
|
|
return map..[key] = ${rc.pascalCase}Serializer.toMap(model.${field
|
|
|
|
.name}[key]);
|
2018-02-28 01:43:43 +00:00
|
|
|
})''';
|
2018-02-28 01:41:19 +00:00
|
|
|
}
|
2018-02-28 01:18:48 +00:00
|
|
|
}
|
|
|
|
|
2018-02-28 01:10:57 +00:00
|
|
|
buf.write("'$alias': $serializedRepresentation");
|
|
|
|
}
|
|
|
|
|
|
|
|
buf.write('};');
|
|
|
|
method.body = new Code(buf.toString());
|
2018-02-28 00:59:43 +00:00
|
|
|
}));
|
|
|
|
}
|
2018-02-28 02:10:43 +00:00
|
|
|
|
|
|
|
void generateFromMapMethod(
|
|
|
|
ClassBuilder clazz, BuildContext ctx, FileBuilder file) {
|
|
|
|
clazz.methods.add(new Method((method) {
|
|
|
|
method
|
|
|
|
..static = true
|
|
|
|
..name = 'fromMap'
|
|
|
|
..returns = ctx.modelClassType
|
|
|
|
..requiredParameters.add(
|
|
|
|
new Parameter((b) => b
|
|
|
|
..name = 'map'
|
|
|
|
..type = new Reference('Map')),
|
|
|
|
);
|
|
|
|
|
|
|
|
var buf = new StringBuffer('return new ${ctx.modelClassName}(');
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
// Add named parameters
|
|
|
|
for (var field in ctx.fields) {
|
2018-03-02 18:38:41 +00:00
|
|
|
if (ctx.excluded[field.name]?.canDeserialize == false) continue;
|
2018-02-28 02:10:43 +00:00
|
|
|
|
|
|
|
var alias = ctx.resolveFieldName(field.name);
|
|
|
|
method.optionalParameters.add(new Parameter((b) {
|
|
|
|
b
|
|
|
|
..name = field.name
|
|
|
|
..named = true
|
|
|
|
..type = convertTypeReference(field.type);
|
|
|
|
}));
|
|
|
|
|
|
|
|
if (i++ > 0) buf.write(', ');
|
|
|
|
|
|
|
|
String deserializedRepresentation = "map['$alias']";
|
|
|
|
|
|
|
|
// Deserialize dates
|
|
|
|
if (dateTimeTypeChecker.isAssignableFromType(field.type))
|
|
|
|
deserializedRepresentation =
|
|
|
|
"map['$alias'] != null ? DateTime.parse(map['$alias']) : null";
|
|
|
|
|
|
|
|
// Serialize model classes via `XSerializer.toMap`
|
|
|
|
else if (isModelClass(field.type)) {
|
|
|
|
var rc = new ReCase(field.type.name);
|
|
|
|
deserializedRepresentation = "map['$alias'] != null"
|
|
|
|
" ? ${rc.pascalCase}Serializer.fromMap(map['$alias'])"
|
|
|
|
" : null";
|
|
|
|
} else if (field.type is InterfaceType) {
|
|
|
|
var t = field.type as InterfaceType;
|
|
|
|
|
|
|
|
if (t.name == 'List' && t.typeArguments.length == 1) {
|
|
|
|
var rc = new ReCase(t.typeArguments[0].name);
|
|
|
|
deserializedRepresentation = "map['$alias'] is Iterable"
|
|
|
|
" ? map['$alias'].map(${rc
|
|
|
|
.pascalCase}Serializer.fromMap).toList()"
|
|
|
|
" : null";
|
|
|
|
} else if (isMapToModelType(t)) {
|
|
|
|
var rc = new ReCase(t.typeArguments[1].name);
|
|
|
|
deserializedRepresentation = '''
|
|
|
|
map['$alias'] is Map
|
|
|
|
? map['$alias'].keys.fold({}, (out, key) {
|
|
|
|
return out..[key] = ${rc
|
|
|
|
.pascalCase}Serializer.fromMap(map['$alias'][key]);
|
|
|
|
})
|
|
|
|
: null
|
|
|
|
''';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buf.write('${field.name}: $deserializedRepresentation');
|
|
|
|
}
|
|
|
|
|
|
|
|
buf.write(');');
|
|
|
|
method.body = new Code(buf.toString());
|
|
|
|
}));
|
|
|
|
}
|
2018-02-28 00:59:43 +00:00
|
|
|
}
|