{ *************************************************************************** Copyright (c) 2015-2018 Kike Pérez Unit : Quick.AutoMapper Description : Auto Mapper object properties Author : Kike Pérez Version : 1.0 Created : 25/08/2018 Modified : 30/08/2018 This file is part of QuickLib: https://github.com/exilon/QuickLib *************************************************************************** Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. *************************************************************************** } unit Quick.AutoMapper; interface uses SysUtils, Generics.Collections, {$IFDEF FPC} typinfo, {$ENDIF} RTTI; type TCustomMapping = class private fMapDictionary : TDictionary; public constructor Create; destructor Destroy; override; procedure AddMap(const aName, aMapName : string); function GetMap(const aName : string; out vMapName : string) : Boolean; end; TObjMapper = class public class procedure Map(aSrcObj : TObject; aTgtObj : TObject; aCustomMapping: TCustomMapping = nil); end; TMapper = class public class function Map(aSrcObj : TObject; aCustomMapping: TCustomMapping = nil): T; overload; class procedure Map(aSrcObj : TObject; aTgtObj : T; aCustomMapping : TCustomMapping = nil); overload; end; TAutoMapper = class private fCustomMapping : TCustomMapping; public constructor Create; destructor Destroy; override; property CustomMapping : TCustomMapping read fCustomMapping write fCustomMapping; function Map(aSrcObj : TClass1) : TClass2; overload; {$IFNDEF FPC} function Map(aSrcObj : TClass2) : TClass1; overload; {$ELSE} //freepascal detects overload with generic types as duplicated function, added dummy field to avoid this function Map(aSrcObj : TClass2; dummy : Boolean = True) : TClass1; overload; {$ENDIF} end; EAutoMapperError = class(Exception); implementation { TObjMapper } class procedure TObjMapper.Map(aSrcObj : TObject; aTgtObj : TObject; aCustomMapping: TCustomMapping = nil); var ctx : TRttiContext; rType : TRttiType; tgtprop : TRttiProperty; mapname : string; obj : TObject; begin if aTgtObj = nil then aTgtObj := aTgtObj.ClassType.Create; for tgtprop in ctx.GetType(aTgtObj.ClassInfo).GetProperties do begin if tgtprop.IsWritable then begin if not tgtprop.PropertyType.IsInstance then begin rType := ctx.GetType(aSrcObj.ClassInfo); if Assigned(aCustomMapping) then begin if aCustomMapping.GetMap(tgtprop.Name,mapname) then begin if rType.GetProperty(mapname) = nil then raise EAutoMapperError.CreateFmt('No valid custom mapping (Source: %s - Target: %s)',[mapname,tgtprop.Name]); {$IFNDEF FPC} tgtprop.SetValue(aTgtObj,rType.GetProperty(mapname).GetValue(aSrcObj)) {$ELSE} SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,mapname)); {$ENDIF} end else begin if rType.GetProperty(tgtprop.Name) <> nil then try {$IFNDEF FPC} tgtprop.SetValue(aTgtObj,rType.GetProperty(tgtprop.Name).GetValue(aSrcObj)); {$ELSE} SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,tgtprop.Name)); {$ENDIF} except on E : Exception do raise EAUtoMapperError.CreateFmt('Error mapping property "%s" : %s',[tgtprop.Name,e.message]); end; end; end else begin 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} except on E : Exception do raise EAUtoMapperError.CreateFmt('Error mapping property "%s" : %s',[tgtprop.Name,e.message]); end; end; end else begin obj := tgtprop.GetValue(aTgtObj).AsObject; {$IFNDEF FPC} if obj = nil then obj := TObject.Create; {$ELSE} if obj = nil then obj := GetObjectProp(aSrcObj,tgtprop.Name).ClassType.Create; {$ENDIF} if obj <> nil then begin {$IFNDEF FPC} TObjMapper.Map(rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject,obj,aCustomMapping); {$ELSE} TObjMapper.Map(GetObjectProp(aSrcObj,tgtprop.Name),obj,aCustomMapping); SetObjectProp(aTgtObj,tgtprop.Name,obj); {$ENDIF} end else raise EAutoMapperError.CreateFmt('Target object "%s" not autocreated by class',[tgtprop.Name]); end; end; end; end; class function TMapper.Map(aSrcObj : TObject; aCustomMapping: TCustomMapping = nil) : T; var obj : T; begin obj := T.Create; TObjMapper.Map(aSrcObj,obj,aCustomMapping); Result := obj; end; class procedure TMapper.Map(aSrcObj : TObject; aTgtObj : T; aCustomMapping : TCustomMapping = nil); begin TObjMapper.Map(aSrcObj, aTgtObj, aCustomMapping); end; { TAutoMapper } constructor TAutoMapper.Create; begin fCustomMapping := TCustomMapping.Create; end; destructor TAutoMapper.Destroy; begin if Assigned(fCustomMapping) then fCustomMapping.Free; inherited; end; {} function TAutoMapper.Map(aSrcObj: TClass1): TClass2; begin Result := TMapper.Map(aSrcObj,fCustomMapping); end; {$IFNDEF FPC} function TAutoMapper.Map(aSrcObj: TClass2): TClass1; {$ELSE} function TAutoMapper.Map(aSrcObj: TClass2; dummy : Boolean = True): TClass1; {$ENDIF} begin Result := TMapper.Map(aSrcObj,fCustomMapping); end; { TCustomMappingFields } procedure TCustomMapping.AddMap(const aName, aMapName: string); begin //add map fields fMapDictionary.Add(aName,aMapName); //add reverse lookup fMapDictionary.Add(aMapName,aName); end; constructor TCustomMapping.Create; begin fMapDictionary := TDictionary.Create; end; destructor TCustomMapping.Destroy; begin fMapDictionary.Free; inherited; end; function TCustomMapping.GetMap(const aName: string; out vMapName: string): Boolean; begin Result := fMapDictionary.TryGetValue(aName,vMapName); end; end.