Browse Source

AutoMapper custom mapping functions

Unknown 6 years ago
parent
commit
56b61fab65
1 changed files with 164 additions and 28 deletions
  1. 164 28
      Quick.AutoMapper.pas

+ 164 - 28
Quick.AutoMapper.pas

@@ -5,9 +5,9 @@
   Unit        : Quick.AutoMapper
   Unit        : Quick.AutoMapper
   Description : Auto Mapper object properties
   Description : Auto Mapper object properties
   Author      : Kike Pérez
   Author      : Kike Pérez
-  Version     : 1.1
+  Version     : 1.2
   Created     : 25/08/2018
   Created     : 25/08/2018
-  Modified    : 23/09/2018
+  Modified    : 31/03/2019
 
 
   This file is part of QuickLib: https://github.com/exilon/QuickLib
   This file is part of QuickLib: https://github.com/exilon/QuickLib
 
 
@@ -36,13 +36,29 @@ interface
 uses
 uses
   SysUtils,
   SysUtils,
   Generics.Collections,
   Generics.Collections,
-  //{$IFDEF FPC}
   typinfo,
   typinfo,
-  //{$ENDIF}
+  Quick.Value,
+  {$IFDEF FPC}
+  Variants,
+  {$ENDIF}
   RTTI;
   RTTI;
 
 
 type
 type
 
 
+  {$IFNDEF FPC}
+  TFlexValue = TValue;
+  {$ELSE}
+  TFlexValue = variant;
+  {$ENDIF}
+
+  {$IFNDEF FPC}
+  TMappingProc<TClass1> = reference to procedure(const aSrcObj : TClass1; const aTargetName : string; out Value : TFlexValue);
+  TAfterMappingProc<TClass1,TClass2> = reference to procedure(const aSrcObj : TClass1; aTgtObj : TClass2);
+  {$ELSE}
+  TMappingProc<TObject> = procedure(const aSrcObj : TObject; const aTargetName : string; out Value : TFlexValue) of object;
+  TAfterMappingProc<TClass1,TClass2> = procedure(const aSrcObj : TClass1; aTgtObj : TClass2) of object;
+  {$ENDIF}
+
   TCustomMapping = class
   TCustomMapping = class
   private
   private
     fMapDictionary : TDictionary<string,string>;
     fMapDictionary : TDictionary<string,string>;
@@ -55,7 +71,12 @@ type
 
 
   TObjMapper = class
   TObjMapper = class
   public
   public
-    class procedure Map(aSrcObj : TObject; aTgtObj : TObject; aCustomMapping: TCustomMapping = nil);
+    class procedure Map(aSrcObj : TObject; aTgtObj : TObject; aCustomMapping: TCustomMapping = nil); overload;
+    {$IFNDEF FPC}
+    class procedure Map<Tm>(aSrcObj : TObject; aTgtObj : TObject; aDoMappingProc : TMappingProc<Tm>; aCustomMapping: TCustomMapping = nil); overload;
+    {$ELSE}
+    class procedure Map(aSrcObj : TObject; aTgtObj : TObject; aDoMappingProc : TMappingProc<TObject>; aCustomMapping: TCustomMapping = nil); overload;
+    {$ENDIF}
   end;
   end;
 
 
   TListMapper = class
   TListMapper = class
@@ -65,22 +86,46 @@ type
 
 
   TObjListMapper = class
   TObjListMapper = class
   public
   public
-    class procedure Map(aSrcObjList : TObject; aTgtObjList : TObject; aCustomMapping : TCustomMapping = nil);
+    class procedure Map(aSrcObjList : TObject; aTgtObjList : TObject; aCustomMapping : TCustomMapping = nil); overload;
+    {$IFNDEF FPC}
+    class procedure Map<Tm>(aSrcObjList : TObject; aTgtObjList : TObject; aDoMappingProc : TMappingProc<Tm>; aCustomMapping : TCustomMapping = nil); overload;
+    {$ELSE}
+    class procedure Map(aSrcObjList : TObject; aTgtObjList : TObject; aDoMappingProc : TMappingProc<TObject>; aCustomMapping : TCustomMapping = nil); overload;
+    {$ENDIF}
   end;
   end;
 
 
   TMapper<T : class, constructor> = class
   TMapper<T : class, constructor> = class
   public
   public
     class function Map(aSrcObj : TObject; aCustomMapping: TCustomMapping = nil): T; overload;
     class function Map(aSrcObj : TObject; aCustomMapping: TCustomMapping = nil): T; overload;
     class procedure Map(aSrcObj : TObject; aTgtObj : T; aCustomMapping : TCustomMapping = nil); overload;
     class procedure Map(aSrcObj : TObject; aTgtObj : T; aCustomMapping : TCustomMapping = nil); overload;
+    {$IFNDEF FPC}
+    class function Map<Tm>(aSrcObj : TObject; aDoMappingProc : TMappingProc<Tm>; aCustomMapping: TCustomMapping = nil): T; overload;
+    class procedure Map<Tm>(aSrcObj : TObject; aTgtObj : T; aDoMappingProc : TMappingProc<Tm>; aCustomMapping: TCustomMapping = nil); overload;
+    {$ELSE}
+    class function Map(aSrcObj : TObject; aDoMappingProc : TMappingProc<TObject>; aCustomMapping: TCustomMapping = nil): T; overload;
+    class procedure Map(aSrcObj : TObject; aTgtObj : T; aDoMappingProc : TMappingProc<TObject>; aCustomMapping: TCustomMapping);
+    {$ENDIF}
   end;
   end;
 
 
   TAutoMapper<TClass1, TClass2 : class, constructor> = class
   TAutoMapper<TClass1, TClass2 : class, constructor> = class
   private
   private
     fCustomMapping : TCustomMapping;
     fCustomMapping : TCustomMapping;
+    {$IFNDEF FPC}
+    fOnDoMapping : TMappingProc<TClass1>;
+    {$ELSE}
+    fOnDoMapping : TMappingProc<TObject>;
+    {$ENDIF}
+    fOnAfterMapping : TAfterMappingProc<TClass1,TClass2>;
   public
   public
     constructor Create;
     constructor Create;
     destructor Destroy; override;
     destructor Destroy; override;
     property CustomMapping : TCustomMapping read fCustomMapping write fCustomMapping;
     property CustomMapping : TCustomMapping read fCustomMapping write fCustomMapping;
+    {$IFNDEF FPC}
+    property OnDoMapping : TMappingProc<TClass1> read fOnDoMapping write fOnDoMapping;
+    {$ELSE}
+    property OnDoMapping : TMappingProc<TObject> read fOnDoMapping write fOnDoMapping;
+    {$ENDIF}
+    property OnAfterMapping : TAfterMappingProc<TClass1,TClass2> read fOnAfterMapping write fOnAfterMapping;
     function Map(aSrcObj : TClass1) : TClass2; overload;
     function Map(aSrcObj : TClass1) : TClass2; overload;
     {$IFNDEF FPC}
     {$IFNDEF FPC}
     function Map(aSrcObj : TClass2) : TClass1; overload;
     function Map(aSrcObj : TClass2) : TClass1; overload;
@@ -97,26 +142,42 @@ implementation
 { TObjMapper }
 { TObjMapper }
 
 
 class procedure TObjMapper.Map(aSrcObj : TObject; aTgtObj : TObject; aCustomMapping: TCustomMapping = nil);
 class procedure TObjMapper.Map(aSrcObj : TObject; aTgtObj : TObject; aCustomMapping: TCustomMapping = nil);
+begin
+  Map{$IFNDEF FPC}<TObject>{$ENDIF}(aSrcObj,aTgtObj,nil,aCustomMapping);
+end;
+
+{$IFNDEF FPC}
+class procedure TObjMapper.Map<Tm>(aSrcObj : TObject; aTgtObj : TObject; aDoMappingProc : TMappingProc<Tm>; aCustomMapping: TCustomMapping = nil);
+{$ELSE}
+class procedure TObjMapper.Map(aSrcObj : TObject; aTgtObj : TObject; aDoMappingProc : TMappingProc<TObject>; aCustomMapping: TCustomMapping = nil);
+{$ENDIF}
 var
 var
   ctx : TRttiContext;
   ctx : TRttiContext;
   rType : TRttiType;
   rType : TRttiType;
   tgtprop : TRttiProperty;
   tgtprop : TRttiProperty;
   mapname : string;
   mapname : string;
   obj : TObject;
   obj : TObject;
+  manualmapping : Boolean;
+  value : TFlexValue;
+  {$IFNDEF FPC}
   clname : string;
   clname : string;
+  objvalue : TValue;
+  {$ENDIF}
 begin
 begin
-  if aTgtObj = nil then aTgtObj := GetTypeData(aTgtObj.ClassInfo).classType.Create;
+  //if aTgtObj = nil then aTgtObj := GetTypeData(aTgtObj.ClassInfo).classType.Create;
+  if aTgtObj = nil then raise EAutoMapperError.Create('TObjMapper: Target Object passed must be created before');
 
 
+  {$IFNDEF FPC}
+  objvalue := TValue.From(aSrcObj);
+  {$ENDIF}
   rType := ctx.GetType(aSrcObj.ClassInfo);
   rType := ctx.GetType(aSrcObj.ClassInfo);
   for tgtprop in ctx.GetType(aTgtObj.ClassInfo).GetProperties do
   for tgtprop in ctx.GetType(aTgtObj.ClassInfo).GetProperties do
   begin
   begin
     if tgtprop.IsWritable then
     if tgtprop.IsWritable then
     begin
     begin
-      if tgtprop.Name = 'Agent'
-        then Sleep(0);
       if not tgtprop.PropertyType.IsInstance then
       if not tgtprop.PropertyType.IsInstance then
       begin
       begin
-        if Assigned(aCustomMapping) then
+        if Assigned(aCustomMapping) and (not Assigned(aDoMappingProc)) then
         begin
         begin
           if aCustomMapping.GetMap(tgtprop.Name,mapname) then
           if aCustomMapping.GetMap(tgtprop.Name,mapname) then
           begin
           begin
@@ -144,11 +205,34 @@ begin
         else
         else
         begin
         begin
           try
           try
-            {$IFNDEF FPC}
-            if rType.GetProperty(tgtprop.Name) <> nil then tgtprop.SetValue(aTgtObj,rType.GetProperty(tgtprop.Name).GetValue(aSrcObj));
-            {$ELSE}
-            if rType.GetProperty(tgtprop.Name) <> nil then SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,tgtprop.Name));
-            {$ENDIF}
+            if Assigned(aDoMappingProc) then
+            begin
+              {$IFNDEF FPC}
+              aDoMappingProc(objvalue.AsType<Tm>,tgtprop.Name,value);
+              manualmapping := not value.IsEmpty;
+              {$ELSE}
+              aDoMappingProc(aSrcObj,tgtprop.Name,value);
+              manualmapping := not varType(value) = varEmpty;
+              {$ENDIF}
+            end
+            else manualmapping := False;
+
+            if manualmapping then
+            begin
+              {$IFNDEF FPC}
+              tgtprop.SetValue(aTgtObj,value);
+              {$ELSE}
+              SetPropValue(aTgtObj,tgtprop.Name,value);
+              {$ENDIF}
+            end
+            else
+            begin
+              {$IFNDEF FPC}
+              if rType.GetProperty(tgtprop.Name) <> nil then tgtprop.SetValue(aTgtObj,rType.GetProperty(tgtprop.Name).GetValue(aSrcObj));
+              {$ELSE}
+              if rType.GetProperty(tgtprop.Name) <> nil then SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,tgtprop.Name));
+              {$ENDIF}
+            end;
           except
           except
             on E : Exception do raise EAUtoMapperError.CreateFmt('Error mapping property "%s" : %s',[tgtprop.Name,e.message]);
             on E : Exception do raise EAUtoMapperError.CreateFmt('Error mapping property "%s" : %s',[tgtprop.Name,e.message]);
           end;
           end;
@@ -168,8 +252,7 @@ begin
           {$IFNDEF FPC}
           {$IFNDEF FPC}
           clname := rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject.ClassName;
           clname := rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject.ClassName;
           if clname.StartsWith('TObjectList') then TObjListMapper.Map(rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject,obj,aCustomMapping)
           if clname.StartsWith('TObjectList') then TObjListMapper.Map(rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject,obj,aCustomMapping)
-            else if clname.StartsWith('TList') then TListMapper.Map(rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject,obj,aCustomMapping)
-              else TObjMapper.Map(rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject,obj,aCustomMapping)
+            else TObjMapper.Map(rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject,obj,aCustomMapping)
           {$ELSE}
           {$ELSE}
           TObjMapper.Map(GetObjectProp(aSrcObj,tgtprop.Name),obj,aCustomMapping);
           TObjMapper.Map(GetObjectProp(aSrcObj,tgtprop.Name),obj,aCustomMapping);
           SetObjectProp(aTgtObj,tgtprop.Name,obj);
           SetObjectProp(aTgtObj,tgtprop.Name,obj);
@@ -182,45 +265,93 @@ begin
 end;
 end;
 
 
 class function TMapper<T>.Map(aSrcObj : TObject; aCustomMapping: TCustomMapping = nil) : T;
 class function TMapper<T>.Map(aSrcObj : TObject; aCustomMapping: TCustomMapping = nil) : T;
+begin
+  Result := Map{$IFNDEF FPC}<TObject>{$ENDIF}(aSrcObj,nil,aCustomMapping);
+end;
+
+{$IFNDEF FPC}
+class function TMapper<T>.Map<Tm>(aSrcObj : TObject; aDoMappingProc : TMappingProc<Tm>; aCustomMapping: TCustomMapping = nil): T;
+{$ELSE}
+class function TMapper<T>.Map(aSrcObj : TObject; aDoMappingProc : TMappingProc<TObject>; aCustomMapping: TCustomMapping = nil): T;
+{$ENDIF}
 var
 var
   obj : T;
   obj : T;
 begin
 begin
   obj := T.Create;
   obj := T.Create;
-  TObjMapper.Map(aSrcObj,obj,aCustomMapping);
+  {$IFNDEF FPC}
+  TObjMapper.Map<Tm>(aSrcObj,obj,aDoMappingProc,aCustomMapping);
+  {$ELSE}
+  TObjMapper.Map(aSrcObj,obj,aDoMappingProc,aCustomMapping);
+  {$ENDIF}
   Result := obj;
   Result := obj;
 end;
 end;
 
 
 class procedure TMapper<T>.Map(aSrcObj : TObject; aTgtObj : T; aCustomMapping : TCustomMapping = nil);
 class procedure TMapper<T>.Map(aSrcObj : TObject; aTgtObj : T; aCustomMapping : TCustomMapping = nil);
 begin
 begin
-  TObjMapper.Map(aSrcObj, aTgtObj, aCustomMapping);
+  {$IFNDEF FPC}
+  Map<T>(aSrcObj,aTgtObj,nil,aCustomMapping);
+  {$ELSE}
+  Map(aSrcObj,aTgtObj,nil,aCustomMapping);
+  {$ENDIF}
 end;
 end;
 
 
+{$IFNDEF FPC}
+class procedure TMapper<T>.Map<Tm>(aSrcObj : TObject; aTgtObj : T; aDoMappingProc : TMappingProc<Tm>; aCustomMapping : TCustomMapping = nil);
+{$ELSE}
+class procedure TMapper<T>.Map(aSrcObj : TObject; aTgtObj : T; aDoMappingProc : TMappingProc<TObject>; aCustomMapping : TCustomMapping);
+{$ENDIF}
+begin
+  {$IFNDEF FPC}
+  TObjMapper.Map<Tm>(aSrcObj, aTgtObj, aDoMappingProc, aCustomMapping);
+  {$ELSE}
+  TObjMapper.Map(aSrcObj, aTgtObj, aDoMappingProc, aCustomMapping);
+  {$ENDIF}
+end;
+
+
 { TAutoMapper<TClass1, TClass2> }
 { TAutoMapper<TClass1, TClass2> }
 
 
 constructor TAutoMapper<TClass1, TClass2>.Create;
 constructor TAutoMapper<TClass1, TClass2>.Create;
 begin
 begin
   fCustomMapping := TCustomMapping.Create;
   fCustomMapping := TCustomMapping.Create;
+  fOnDoMapping := nil;
+  fOnAfterMapping := nil;
 end;
 end;
 
 
 destructor TAutoMapper<TClass1, TClass2>.Destroy;
 destructor TAutoMapper<TClass1, TClass2>.Destroy;
 begin
 begin
   if Assigned(fCustomMapping) then fCustomMapping.Free;
   if Assigned(fCustomMapping) then fCustomMapping.Free;
+  fOnDoMapping := nil;
+  fOnAfterMapping := nil;
   inherited;
   inherited;
 end;
 end;
 
 
 function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass1): TClass2;
 function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass1): TClass2;
+var
+  objvalue : TValue;
+  obj : TObject;
 begin
 begin
-  Result := TMapper<TClass2>.Map(aSrcObj,fCustomMapping);
+  obj := aSrcObj as TObject;
+  //objvalue := TValue.From(aSrcObj).AsObject;
+  {$IFNDEF FPC}
+  Result := TMapper<TClass2>.Map<TClass1>(obj,fOnDoMapping,fCustomMapping);
+  {$ELSE}
+  Result := TMapper<TClass2>.Map(obj,fOnDoMapping,fCustomMapping);
+  {$ENDIF}
+  if Assigned(fOnAfterMapping) then fOnAfterMapping(aSrcObj,Result);
 end;
 end;
 
 
 {$IFNDEF FPC}
 {$IFNDEF FPC}
 function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass2): TClass1;
 function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass2): TClass1;
+begin
+  Result := TMapper<TClass1>.Map<TClass1>(aSrcObj,fOnDoMapping,fCustomMapping);
+end;
 {$ELSE}
 {$ELSE}
 function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass2; dummy : Boolean = True): TClass1;
 function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass2; dummy : Boolean = True): TClass1;
-{$ENDIF}
 begin
 begin
-  Result := TMapper<TClass1>.Map(aSrcObj,fCustomMapping);
+  Result := TMapper<TClass1>.Map(aSrcObj,fOnDoMapping,fCustomMapping);
 end;
 end;
+{$ENDIF}
 
 
 { TCustomMappingFields }
 { TCustomMappingFields }
 
 
@@ -283,11 +414,6 @@ begin
         TObjMapper.Map(value.GetArrayElement(i).AsObject,obj,aCustomMapping);
         TObjMapper.Map(value.GetArrayElement(i).AsObject,obj,aCustomMapping);
         TList<TObject>(aTgtList).Add(obj);
         TList<TObject>(aTgtList).Add(obj);
       end
       end
-      else if typinfo.Kind = tkRecord then
-      begin
-        valuecop := value.GetArrayElement(i);
-        //??
-      end
       else
       else
       begin
       begin
         valuecop := value.GetArrayElement(i);
         valuecop := value.GetArrayElement(i);
@@ -310,7 +436,16 @@ end;
 { TObjListMapper }
 { TObjListMapper }
 
 
 class procedure TObjListMapper.Map(aSrcObjList, aTgtObjList: TObject; aCustomMapping: TCustomMapping);
 class procedure TObjListMapper.Map(aSrcObjList, aTgtObjList: TObject; aCustomMapping: TCustomMapping);
+begin
+  {$IFNDEF FPC}
+  Map<TObject>(aSrcObjList,aTgtObjList,nil,aCustomMapping);
+  {$ELSE}
+  Map(aSrcObjList,aTgtObjList,nil,aCustomMapping);
+  {$ENDIF}
+end;
+
 {$IFNDEF FPC}
 {$IFNDEF FPC}
+class procedure TObjListMapper.Map<Tm>(aSrcObjList : TObject; aTgtObjList : TObject; aDoMappingProc : TMappingProc<Tm>; aCustomMapping : TCustomMapping = nil);
 var
 var
   rtype: TRttiType;
   rtype: TRttiType;
   rtype2 : TRttiType;
   rtype2 : TRttiType;
@@ -336,12 +471,13 @@ begin
     for i := 0 to value.GetArrayLength - 1 do
     for i := 0 to value.GetArrayLength - 1 do
     begin
     begin
       obj := typinfo.TypeData.ClassType.Create;
       obj := typinfo.TypeData.ClassType.Create;
-      TObjMapper.Map(value.GetArrayElement(i).AsObject,obj,aCustomMapping);
+      TObjMapper.Map<Tm>(value.GetArrayElement(i).AsObject,obj,aDoMappingProc,aCustomMapping);
       TObjectList<TObject>(aTgtObjList).Add(obj);
       TObjectList<TObject>(aTgtObjList).Add(obj);
     end;
     end;
   end;
   end;
 end;
 end;
 {$ELSE}
 {$ELSE}
+class procedure TObjListMapper.Map(aSrcObjList : TObject; aTgtObjList : TObject; aDoMappingProc : TMappingProc<TObject>; aCustomMapping : TCustomMapping = nil);
 begin
 begin
 
 
 end;
 end;