Quick.AutoMapper.pas 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. { ***************************************************************************
  2. Copyright (c) 2015-2018 Kike Pérez
  3. Unit : Quick.AutoMapper
  4. Description : Auto Mapper object properties
  5. Author : Kike Pérez
  6. Version : 1.0
  7. Created : 25/08/2018
  8. Modified : 30/08/2018
  9. This file is part of QuickLib: https://github.com/exilon/QuickLib
  10. ***************************************************************************
  11. Licensed under the Apache License, Version 2.0 (the "License");
  12. you may not use this file except in compliance with the License.
  13. You may obtain a copy of the License at
  14. http://www.apache.org/licenses/LICENSE-2.0
  15. Unless required by applicable law or agreed to in writing, software
  16. distributed under the License is distributed on an "AS IS" BASIS,
  17. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. See the License for the specific language governing permissions and
  19. limitations under the License.
  20. *************************************************************************** }
  21. unit Quick.AutoMapper;
  22. {$i QuickLib.inc}
  23. interface
  24. uses
  25. SysUtils,
  26. Generics.Collections,
  27. {$IFDEF FPC}
  28. typinfo,
  29. {$ENDIF}
  30. RTTI;
  31. type
  32. TCustomMapping = class
  33. private
  34. fMapDictionary : TDictionary<string,string>;
  35. public
  36. constructor Create;
  37. destructor Destroy; override;
  38. procedure AddMap(const aName, aMapName : string);
  39. function GetMap(const aName : string; out vMapName : string) : Boolean;
  40. end;
  41. TObjMapper = class
  42. public
  43. class procedure Map(aSrcObj : TObject; aTgtObj : TObject; aCustomMapping: TCustomMapping = nil);
  44. end;
  45. TMapper<T : class, constructor> = class
  46. public
  47. class function Map(aSrcObj : TObject; aCustomMapping: TCustomMapping = nil): T; overload;
  48. class procedure Map(aSrcObj : TObject; aTgtObj : T; aCustomMapping : TCustomMapping = nil); overload;
  49. end;
  50. TAutoMapper<TClass1, TClass2 : class, constructor> = class
  51. private
  52. fCustomMapping : TCustomMapping;
  53. public
  54. constructor Create;
  55. destructor Destroy; override;
  56. property CustomMapping : TCustomMapping read fCustomMapping write fCustomMapping;
  57. function Map(aSrcObj : TClass1) : TClass2; overload;
  58. {$IFNDEF FPC}
  59. function Map(aSrcObj : TClass2) : TClass1; overload;
  60. {$ELSE}
  61. //freepascal detects overload with generic types as duplicated function, added dummy field to avoid this
  62. function Map(aSrcObj : TClass2; dummy : Boolean = True) : TClass1; overload;
  63. {$ENDIF}
  64. end;
  65. EAutoMapperError = class(Exception);
  66. implementation
  67. { TObjMapper }
  68. class procedure TObjMapper.Map(aSrcObj : TObject; aTgtObj : TObject; aCustomMapping: TCustomMapping = nil);
  69. var
  70. ctx : TRttiContext;
  71. rType : TRttiType;
  72. tgtprop : TRttiProperty;
  73. mapname : string;
  74. obj : TObject;
  75. begin
  76. if aTgtObj = nil then aTgtObj := aTgtObj.ClassType.Create;
  77. for tgtprop in ctx.GetType(aTgtObj.ClassInfo).GetProperties do
  78. begin
  79. if tgtprop.IsWritable then
  80. begin
  81. if not tgtprop.PropertyType.IsInstance then
  82. begin
  83. rType := ctx.GetType(aSrcObj.ClassInfo);
  84. if Assigned(aCustomMapping) then
  85. begin
  86. if aCustomMapping.GetMap(tgtprop.Name,mapname) then
  87. begin
  88. if rType.GetProperty(mapname) = nil then raise EAutoMapperError.CreateFmt('No valid custom mapping (Source: %s - Target: %s)',[mapname,tgtprop.Name]);
  89. {$IFNDEF FPC}
  90. tgtprop.SetValue(aTgtObj,rType.GetProperty(mapname).GetValue(aSrcObj))
  91. {$ELSE}
  92. SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,mapname));
  93. {$ENDIF}
  94. end
  95. else
  96. begin
  97. if rType.GetProperty(tgtprop.Name) <> nil then
  98. try
  99. {$IFNDEF FPC}
  100. tgtprop.SetValue(aTgtObj,rType.GetProperty(tgtprop.Name).GetValue(aSrcObj));
  101. {$ELSE}
  102. SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,tgtprop.Name));
  103. {$ENDIF}
  104. except
  105. on E : Exception do raise EAUtoMapperError.CreateFmt('Error mapping property "%s" : %s',[tgtprop.Name,e.message]);
  106. end;
  107. end;
  108. end
  109. else
  110. begin
  111. try
  112. {$IFNDEF FPC}
  113. if rType.GetProperty(tgtprop.Name) <> nil then tgtprop.SetValue(aTgtObj,rType.GetProperty(tgtprop.Name).GetValue(aSrcObj));
  114. {$ELSE}
  115. if rType.GetProperty(tgtprop.Name) <> nil then SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,tgtprop.Name));
  116. {$ENDIF}
  117. except
  118. on E : Exception do raise EAUtoMapperError.CreateFmt('Error mapping property "%s" : %s',[tgtprop.Name,e.message]);
  119. end;
  120. end;
  121. end
  122. else
  123. begin
  124. obj := tgtprop.GetValue(aTgtObj).AsObject;
  125. {$IFNDEF FPC}
  126. if obj = nil then obj := TObject.Create;
  127. {$ELSE}
  128. if obj = nil then obj := GetObjectProp(aSrcObj,tgtprop.Name).ClassType.Create;
  129. {$ENDIF}
  130. if obj <> nil then
  131. begin
  132. {$IFNDEF FPC}
  133. TObjMapper.Map(rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject,obj,aCustomMapping);
  134. {$ELSE}
  135. TObjMapper.Map(GetObjectProp(aSrcObj,tgtprop.Name),obj,aCustomMapping);
  136. SetObjectProp(aTgtObj,tgtprop.Name,obj);
  137. {$ENDIF}
  138. end
  139. else raise EAutoMapperError.CreateFmt('Target object "%s" not autocreated by class',[tgtprop.Name]);
  140. end;
  141. end;
  142. end;
  143. end;
  144. class function TMapper<T>.Map(aSrcObj : TObject; aCustomMapping: TCustomMapping = nil) : T;
  145. var
  146. obj : T;
  147. begin
  148. obj := T.Create;
  149. TObjMapper.Map(aSrcObj,obj,aCustomMapping);
  150. Result := obj;
  151. end;
  152. class procedure TMapper<T>.Map(aSrcObj : TObject; aTgtObj : T; aCustomMapping : TCustomMapping = nil);
  153. begin
  154. TObjMapper.Map(aSrcObj, aTgtObj, aCustomMapping);
  155. end;
  156. { TAutoMapper<TClass1, TClass2> }
  157. constructor TAutoMapper<TClass1, TClass2>.Create;
  158. begin
  159. fCustomMapping := TCustomMapping.Create;
  160. end;
  161. destructor TAutoMapper<TClass1, TClass2>.Destroy;
  162. begin
  163. if Assigned(fCustomMapping) then fCustomMapping.Free;
  164. inherited;
  165. end;
  166. {}
  167. function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass1): TClass2;
  168. begin
  169. Result := TMapper<TClass2>.Map(aSrcObj,fCustomMapping);
  170. end;
  171. {$IFNDEF FPC}
  172. function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass2): TClass1;
  173. {$ELSE}
  174. function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass2; dummy : Boolean = True): TClass1;
  175. {$ENDIF}
  176. begin
  177. Result := TMapper<TClass1>.Map(aSrcObj,fCustomMapping);
  178. end;
  179. { TCustomMappingFields }
  180. procedure TCustomMapping.AddMap(const aName, aMapName: string);
  181. begin
  182. //add map fields
  183. fMapDictionary.Add(aName,aMapName);
  184. //add reverse lookup
  185. fMapDictionary.Add(aMapName,aName);
  186. end;
  187. constructor TCustomMapping.Create;
  188. begin
  189. fMapDictionary := TDictionary<string,string>.Create;
  190. end;
  191. destructor TCustomMapping.Destroy;
  192. begin
  193. fMapDictionary.Free;
  194. inherited;
  195. end;
  196. function TCustomMapping.GetMap(const aName: string; out vMapName: string): Boolean;
  197. begin
  198. Result := fMapDictionary.TryGetValue(aName,vMapName);
  199. end;
  200. end.