Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions example/lib/field_mapping/field_mapping.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import 'package:smartstruct/smartstruct.dart';

part 'field_mapping.mapper.g.dart';

enum DogType { happy, angry }

// TARGET

class Dog {
final String name;
final String breed;
final int age;
final DogType dogType;

Dog(this.name, this.breed, this.age);
Dog(this.name, this.breed, this.age, this.dogType);
}

// SOURCE
Expand All @@ -17,14 +21,22 @@ class DogModel {
final String dogName;
final String breed;
final int dogAge;
final String dogType;

DogModel(this.dogName, this.breed, this.dogAge);
DogModel(this.dogName, this.breed, this.dogAge, this.dogType);
}

/// Mapper showcasing explicit fieldmapping in case fields do not match their respective fieldnames
@Mapper()
abstract class DogMapper {
static DogType _mapDogType(DogModel model) {
if (model.dogType == 'angry') return DogType.angry;

return DogType.happy;
}

@Mapping(source: 'dogName', target: 'name')
@Mapping(source: 'dogAge', target: 'age')
@Mapping(source: _mapDogType, target: 'dogType')
Dog fromDogModel(DogModel model);
}
3 changes: 2 additions & 1 deletion example/lib/field_mapping/field_mapping.mapper.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions example/lib/injection/service_locator.config.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions example/lib/list/nullable_list.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:smartstruct/smartstruct.dart';

part 'nullable_list.mapper.g.dart';

class NullableListSource {
final List<ListSubSource>? list;
final List<ListSubSource>? list2;

NullableListSource(this.list, this.list2);
}

class ListSubSource {
final String text;

ListSubSource(this.text);
}

class NullableListTarget {
final List<ListSubTarget>? list;
final List<ListSubTarget> list2;

NullableListTarget(this.list, this.list2);
}

class ListSubTarget {
final String text;

ListSubTarget(this.text);
}

@Mapper()
abstract class NullableListMapper {
NullableListTarget fromSource(NullableListSource source);
ListSubTarget fromSubSource(ListSubSource source);
}
25 changes: 25 additions & 0 deletions example/lib/list/nullable_list.mapper.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

166 changes: 85 additions & 81 deletions generator/lib/code_builders/assignment_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,12 @@ Expression generateSourceFieldAssignment(SourceAssignment sourceAssignment,
expr = refer(sourceFunction.enclosingElement3.name!)
.property(sourceFunction.name);
}
sourceFieldAssignment = expr.call([...references], makeNamedArgumentForStaticFunction(sourceFunction));
sourceFieldAssignment = expr.call(
[...references], makeNamedArgumentForStaticFunction(sourceFunction));

// The return of the function may be needed a nested mapping.
sourceFieldAssignment = invokeNestedMappingForStaticFunction(
sourceFunction,
abstractMapper,
targetField,
sourceFieldAssignment);
sourceFunction, abstractMapper, targetField, sourceFieldAssignment);
} else {
// final sourceClass = sourceAssignment.sourceClass!;
final sourceField = sourceAssignment.field!;
Expand All @@ -54,48 +52,66 @@ Expression generateSourceFieldAssignment(SourceAssignment sourceAssignment,
// mapping expression, default is just the identity,
// for example for primitive types or objects that do not have their own mapping method
var expr = refer('(e) => e');
var sourceIsNullable = sourceListType.nullabilitySuffix == NullabilitySuffix.question;
var targetIsNullable = targetListType.nullabilitySuffix == NullabilitySuffix.question;
var sourceIsNullable =
sourceListType.nullabilitySuffix == NullabilitySuffix.question;
var targetIsNullable =
targetListType.nullabilitySuffix == NullabilitySuffix.question;

var needTargetFilter = sourceIsNullable && !targetIsNullable;
if (matchingMappingListMethods.isNotEmpty) {
final nestedMapping = matchingMappingListMethods.first;
// expr = refer(nestedMapping.name);
final invokeStr = invokeNestedMappingFunction(
nestedMapping,
sourceIsNullable,
refer("x"),
nestedMapping,
sourceIsNullable,
refer("x"),
refer("x"),
).accept(DartEmitter()).toString();
expr = refer('''
(x) => $invokeStr
''');
final returnIsNullable = checkNestMappingReturnNullable(nestedMapping, sourceIsNullable);
needTargetFilter = !targetIsNullable && returnIsNullable;
final returnIsNullable =
checkNestMappingReturnNullable(nestedMapping, sourceIsNullable);
needTargetFilter = !targetIsNullable && returnIsNullable;
}

sourceFieldAssignment =
// source.{field}.map
sourceReference.property(sourceField.name)
.property('map')
// (expr)
.call([expr]);

if(needTargetFilter) {
sourceFieldAssignment = sourceFieldAssignment.property("where").call([refer("(x) => x != null")]);
if (sourceField.type.nullabilitySuffix == NullabilitySuffix.question) {
sourceFieldAssignment =
// source.{field}.map
sourceReference.property(sourceField.name).nullSafeProperty('map')
// (expr)
.call([expr]);
} else {
sourceFieldAssignment =
// source.{field}.map
sourceReference.property(sourceField.name).property('map')
// (expr)
.call([expr]);
}

if(sourceAssignment.needCollect(targetField.type)) {
if (needTargetFilter) {
sourceFieldAssignment = sourceFieldAssignment
//.toList() .toSet()
.property(sourceAssignment.collectInvoke(targetField.type))
// .property('toList')
// .call([])
;
.property("where")
.call([refer("(x) => x != null")]);
}

if(needTargetFilter) {
if (sourceAssignment.needCollect(targetField.type)) {
sourceFieldAssignment = sourceFieldAssignment
.asA(refer(targetField.type.getDisplayString(withNullability: true)));
//.toList() .toSet()
.property(sourceAssignment.collectInvoke(targetField.type))
// .property('toList')
// .call([])
;

if (targetField.type.nullabilitySuffix == NullabilitySuffix.none &&
sourceField.type.nullabilitySuffix == NullabilitySuffix.question) {
sourceFieldAssignment = sourceFieldAssignment.ifNullThen(refer('[]'));
}
}

if (needTargetFilter) {
sourceFieldAssignment = sourceFieldAssignment.asA(
refer(targetField.type.getDisplayString(withNullability: true)));
}
} else {
// found a mapping method in the class which will map the source to target
Expand All @@ -105,7 +121,7 @@ Expression generateSourceFieldAssignment(SourceAssignment sourceAssignment,
// nested classes can be mapped with their own mapping methods
if (matchingMappingMethods.isNotEmpty) {
sourceFieldAssignment = invokeNestedMappingFunction(
matchingMappingMethods.first,
matchingMappingMethods.first,
sourceAssignment.refChain!.isNullable,
refer(sourceAssignment.refChain!.refWithQuestion),
refer(sourceAssignment.refChain!.ref),
Expand All @@ -117,24 +133,19 @@ Expression generateSourceFieldAssignment(SourceAssignment sourceAssignment,
}

Expression invokeNestedMappingFunction(
MethodElement method,
MethodElement method,
bool sourceNullable,
Expression refWithQuestion,
Expression ref,
) {
Expression sourceFieldAssignment;
if(method.parameters.first.isOptional) {
if (method.parameters.first.isOptional) {
// The parameter can be null.
sourceFieldAssignment = refer(method.name)
.call([refWithQuestion]);
sourceFieldAssignment = refer(method.name).call([refWithQuestion]);
} else {
sourceFieldAssignment = refer(method.name)
.call([ref]);
sourceFieldAssignment = refer(method.name).call([ref]);
sourceFieldAssignment = checkNullExpression(
sourceNullable,
refWithQuestion,
sourceFieldAssignment
);
sourceNullable, refWithQuestion, sourceFieldAssignment);
}
return sourceFieldAssignment;
}
Expand All @@ -146,27 +157,24 @@ Expression invokeNestedMappingForStaticFunction(
Expression sourceFieldAssignment,
) {
final returnType = sourceFunction.returnType;
final matchingMappingMethods = _findMatchingMappingMethod(
abstractMapper, targetField.type, returnType);
if(matchingMappingMethods.isNotEmpty) {
final matchingMappingMethods =
_findMatchingMappingMethod(abstractMapper, targetField.type, returnType);
if (matchingMappingMethods.isNotEmpty) {
final nestedMappingMethod = matchingMappingMethods.first;

if(
nestedMappingMethod.parameters.first.type.nullabilitySuffix != NullabilitySuffix.question &&
sourceFunction.returnType.nullabilitySuffix == NullabilitySuffix.question
) {
if (nestedMappingMethod.parameters.first.type.nullabilitySuffix !=
NullabilitySuffix.question &&
sourceFunction.returnType.nullabilitySuffix ==
NullabilitySuffix.question) {
final str = makeNullCheckCall(
sourceFieldAssignment.accept(
DartEmitter()
).toString(),
sourceFieldAssignment.accept(DartEmitter()).toString(),
nestedMappingMethod,
);
sourceFieldAssignment = refer(str);
} else {
sourceFieldAssignment = refer(matchingMappingMethods.first.name)
.call([sourceFieldAssignment]);
}

}
return sourceFieldAssignment;
}
Expand All @@ -180,8 +188,8 @@ Iterable<MethodElement> _findMatchingMappingMethod(ClassElement classElement,
// So ingore the nullability of all the type for the nested mapping function is more easy to be matched.
// The process of nullability is one duty for this library.

if(met.parameters.isEmpty) {
return false;
if (met.parameters.isEmpty) {
return false;
}
final metReturnElement = met.returnType.element2;
final metParameterElement = met.parameters.first.type.element2;
Expand All @@ -204,31 +212,30 @@ Iterable<DartType> _getGenericTypes(DartType type) {

// Sometimes we need to pass some variable to the static function just like "this pointer".
// We can use the named parameters to implement the goal.
Map<String, Expression> makeNamedArgumentForStaticFunction(ExecutableElement element) {

final argumentMap = {
"mapper": "this",
"\$this": "this",
};
final namedParameterList = element.parameters.where((p) => p.isNamed && argumentMap.containsKey(p.name)).toList();
Map<String, Expression> makeNamedArgumentForStaticFunction(
ExecutableElement element) {
final argumentMap = {
"mapper": "this",
"\$this": "this",
};
final namedParameterList = element.parameters
.where((p) => p.isNamed && argumentMap.containsKey(p.name))
.toList();

return namedParameterList
.asMap().map((key, value) => MapEntry(
value.name,
refer(argumentMap[value.name]!)
));
return namedParameterList.asMap().map(
(key, value) => MapEntry(value.name, refer(argumentMap[value.name]!)));
}

Expression checkNullExpression(
bool needCheck,
Expression sourceRef,
Expression expression,
) {
if(needCheck) {
if (needCheck) {
return sourceRef.equalTo(literalNull).conditional(
literalNull,
expression,
);
literalNull,
expression,
);
} else {
return expression;
}
Expand All @@ -238,8 +245,8 @@ makeNullCheckCall(
String checkTarget,
MethodElement method,
) {

final methodInvoke = refer(method.name).call([refer("tmp")]).accept(DartEmitter()).toString();
final methodInvoke =
refer(method.name).call([refer("tmp")]).accept(DartEmitter()).toString();
return '''
(){
final tmp = $checkTarget;
Expand All @@ -249,13 +256,10 @@ makeNullCheckCall(
}

checkNestMappingReturnNullable(MethodElement method, bool inputNullable) {
final returnIsNullable =
(inputNullable &&
method.parameters.first.type.nullabilitySuffix != NullabilitySuffix.question
) ||
(
inputNullable &&
method.returnType.nullabilitySuffix == NullabilitySuffix.question
);
return returnIsNullable;
}
final returnIsNullable = (inputNullable &&
method.parameters.first.type.nullabilitySuffix !=
NullabilitySuffix.question) ||
(inputNullable &&
method.returnType.nullabilitySuffix == NullabilitySuffix.question);
return returnIsNullable;
}
Loading