Quick.AutoMapper.pas 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  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.1
  7. Created : 25/08/2018
  8. Modified : 23/09/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. TListMapper = class
  46. public
  47. class procedure Map(aSrcList, aTgtList: TObject; aCustomMapping: TCustomMapping);
  48. end;
  49. TObjListMapper = class
  50. public
  51. class procedure Map(aSrcObjList : TObject; aTgtObjList : TObject; aCustomMapping : TCustomMapping = nil);
  52. end;
  53. TMapper<T : class, constructor> = class
  54. public
  55. class function Map(aSrcObj : TObject; aCustomMapping: TCustomMapping = nil): T; overload;
  56. class procedure Map(aSrcObj : TObject; aTgtObj : T; aCustomMapping : TCustomMapping = nil); overload;
  57. end;
  58. TAutoMapper<TClass1, TClass2 : class, constructor> = class
  59. private
  60. fCustomMapping : TCustomMapping;
  61. public
  62. constructor Create;
  63. destructor Destroy; override;
  64. property CustomMapping : TCustomMapping read fCustomMapping write fCustomMapping;
  65. function Map(aSrcObj : TClass1) : TClass2; overload;
  66. {$IFNDEF FPC}
  67. function Map(aSrcObj : TClass2) : TClass1; overload;
  68. {$ELSE}
  69. //freepascal detects overload with generic types as duplicated function, added dummy field to avoid this
  70. function Map(aSrcObj : TClass2; dummy : Boolean = True) : TClass1; overload;
  71. {$ENDIF}
  72. end;
  73. EAutoMapperError = class(Exception);
  74. implementation
  75. { TObjMapper }
  76. class procedure TObjMapper.Map(aSrcObj : TObject; aTgtObj : TObject; aCustomMapping: TCustomMapping = nil);
  77. var
  78. ctx : TRttiContext;
  79. rType : TRttiType;
  80. tgtprop : TRttiProperty;
  81. mapname : string;
  82. obj : TObject;
  83. clname : string;
  84. begin
  85. if aTgtObj = nil then aTgtObj := GetTypeData(aTgtObj.ClassInfo).classType.Create;
  86. rType := ctx.GetType(aSrcObj.ClassInfo);
  87. for tgtprop in ctx.GetType(aTgtObj.ClassInfo).GetProperties do
  88. begin
  89. if tgtprop.IsWritable then
  90. begin
  91. if tgtprop.Name = 'Agent'
  92. then Sleep(0);
  93. if not tgtprop.PropertyType.IsInstance then
  94. begin
  95. if Assigned(aCustomMapping) then
  96. begin
  97. if aCustomMapping.GetMap(tgtprop.Name,mapname) then
  98. begin
  99. if rType.GetProperty(mapname) = nil then raise EAutoMapperError.CreateFmt('No valid custom mapping (Source: %s - Target: %s)',[mapname,tgtprop.Name]);
  100. {$IFNDEF FPC}
  101. tgtprop.SetValue(aTgtObj,rType.GetProperty(mapname).GetValue(aSrcObj))
  102. {$ELSE}
  103. SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,mapname));
  104. {$ENDIF}
  105. end
  106. else
  107. begin
  108. if rType.GetProperty(tgtprop.Name) <> nil then
  109. try
  110. {$IFNDEF FPC}
  111. tgtprop.SetValue(aTgtObj,rType.GetProperty(tgtprop.Name).GetValue(aSrcObj));
  112. {$ELSE}
  113. SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,tgtprop.Name));
  114. {$ENDIF}
  115. except
  116. on E : Exception do raise EAUtoMapperError.CreateFmt('Error mapping property "%s" : %s',[tgtprop.Name,e.message]);
  117. end;
  118. end;
  119. end
  120. else
  121. begin
  122. try
  123. {$IFNDEF FPC}
  124. if rType.GetProperty(tgtprop.Name) <> nil then tgtprop.SetValue(aTgtObj,rType.GetProperty(tgtprop.Name).GetValue(aSrcObj));
  125. {$ELSE}
  126. if rType.GetProperty(tgtprop.Name) <> nil then SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,tgtprop.Name));
  127. {$ENDIF}
  128. except
  129. on E : Exception do raise EAUtoMapperError.CreateFmt('Error mapping property "%s" : %s',[tgtprop.Name,e.message]);
  130. end;
  131. end;
  132. end
  133. else
  134. begin
  135. obj := tgtprop.GetValue(aTgtObj).AsObject;
  136. {$IFNDEF FPC}
  137. if obj = nil then obj := TObject.Create;
  138. {$ELSE}
  139. if obj = nil then obj := GetObjectProp(aSrcObj,tgtprop.Name).ClassType.Create;
  140. {$ENDIF}
  141. if obj <> nil then
  142. begin
  143. {$IFNDEF FPC}
  144. clname := rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject.ClassName;
  145. if clname.StartsWith('TObjectList') then TObjListMapper.Map(rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject,obj,aCustomMapping)
  146. else if clname.StartsWith('TList') then TListMapper.Map(rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject,obj,aCustomMapping)
  147. else TObjMapper.Map(rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject,obj,aCustomMapping)
  148. {$ELSE}
  149. TObjMapper.Map(GetObjectProp(aSrcObj,tgtprop.Name),obj,aCustomMapping);
  150. SetObjectProp(aTgtObj,tgtprop.Name,obj);
  151. {$ENDIF}
  152. end
  153. else raise EAutoMapperError.CreateFmt('Target object "%s" not autocreated by class',[tgtprop.Name]);
  154. end;
  155. end;
  156. end;
  157. end;
  158. class function TMapper<T>.Map(aSrcObj : TObject; aCustomMapping: TCustomMapping = nil) : T;
  159. var
  160. obj : T;
  161. begin
  162. obj := T.Create;
  163. TObjMapper.Map(aSrcObj,obj,aCustomMapping);
  164. Result := obj;
  165. end;
  166. class procedure TMapper<T>.Map(aSrcObj : TObject; aTgtObj : T; aCustomMapping : TCustomMapping = nil);
  167. begin
  168. TObjMapper.Map(aSrcObj, aTgtObj, aCustomMapping);
  169. end;
  170. { TAutoMapper<TClass1, TClass2> }
  171. constructor TAutoMapper<TClass1, TClass2>.Create;
  172. begin
  173. fCustomMapping := TCustomMapping.Create;
  174. end;
  175. destructor TAutoMapper<TClass1, TClass2>.Destroy;
  176. begin
  177. if Assigned(fCustomMapping) then fCustomMapping.Free;
  178. inherited;
  179. end;
  180. function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass1): TClass2;
  181. begin
  182. Result := TMapper<TClass2>.Map(aSrcObj,fCustomMapping);
  183. end;
  184. {$IFNDEF FPC}
  185. function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass2): TClass1;
  186. {$ELSE}
  187. function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass2; dummy : Boolean = True): TClass1;
  188. {$ENDIF}
  189. begin
  190. Result := TMapper<TClass1>.Map(aSrcObj,fCustomMapping);
  191. end;
  192. { TCustomMappingFields }
  193. procedure TCustomMapping.AddMap(const aName, aMapName: string);
  194. begin
  195. //add map fields
  196. fMapDictionary.Add(aName,aMapName);
  197. //add reverse lookup
  198. fMapDictionary.Add(aMapName,aName);
  199. end;
  200. constructor TCustomMapping.Create;
  201. begin
  202. fMapDictionary := TDictionary<string,string>.Create;
  203. end;
  204. destructor TCustomMapping.Destroy;
  205. begin
  206. fMapDictionary.Free;
  207. inherited;
  208. end;
  209. function TCustomMapping.GetMap(const aName: string; out vMapName: string): Boolean;
  210. begin
  211. Result := fMapDictionary.TryGetValue(aName,vMapName);
  212. end;
  213. { TListMapper }
  214. class procedure TListMapper.Map(aSrcList, aTgtList: TObject; aCustomMapping: TCustomMapping);
  215. {$IFNDEF FPC}
  216. var
  217. rtype: TRttiType;
  218. rtype2 : TRttiType;
  219. typinfo : PTypeInfo;
  220. methToArray: TRttiMethod;
  221. value: TValue;
  222. valuecop : TValue;
  223. obj : TObject;
  224. i : Integer;
  225. rprop : TRttiProperty;
  226. ctx : TRttiContext;
  227. begin
  228. rtype := ctx.GetType(aSrcList.ClassInfo);
  229. methToArray := rtype.GetMethod('ToArray');
  230. if Assigned(methToArray) then
  231. begin
  232. value := methToArray.Invoke(aSrcList,[]);
  233. Assert(value.IsArray);
  234. rtype2 := ctx.GetType(aTgtList.ClassInfo);
  235. rProp := rtype2.GetProperty('List');
  236. typinfo := GetTypeData(rProp.PropertyType.Handle).DynArrElType^;
  237. for i := 0 to value.GetArrayLength - 1 do
  238. begin
  239. if typinfo.Kind = tkClass then
  240. begin
  241. obj := typinfo.TypeData.ClassType.Create;
  242. TObjMapper.Map(value.GetArrayElement(i).AsObject,obj,aCustomMapping);
  243. TList<TObject>(aTgtList).Add(obj);
  244. end
  245. else if typinfo.Kind = tkRecord then
  246. begin
  247. valuecop := value.GetArrayElement(i);
  248. //??
  249. end
  250. else
  251. begin
  252. valuecop := value.GetArrayElement(i);
  253. case typinfo.Kind of
  254. tkChar, tkString, tkWChar, tkWString : TList<string>(aTgtList).Add(valuecop.AsString);
  255. tkInteger, tkInt64 : TList<Integer>(aTgtList).Add(valuecop.AsInt64);
  256. tkFloat : TList<Extended>(aTgtList).Add(valuecop.AsExtended);
  257. end;
  258. end;
  259. end;
  260. end;
  261. end;
  262. {$ELSE}
  263. begin
  264. end;
  265. {$ENDIF}
  266. { TObjListMapper }
  267. class procedure TObjListMapper.Map(aSrcObjList, aTgtObjList: TObject; aCustomMapping: TCustomMapping);
  268. {$IFNDEF FPC}
  269. var
  270. rtype: TRttiType;
  271. rtype2 : TRttiType;
  272. typinfo : PTypeInfo;
  273. methToArray: TRttiMethod;
  274. value: TValue;
  275. obj : TObject;
  276. i : Integer;
  277. rprop : TRttiProperty;
  278. ctx : TRttiContext;
  279. begin
  280. rtype := ctx.GetType(aSrcObjList.ClassInfo);
  281. methToArray := rtype.GetMethod('ToArray');
  282. if Assigned(methToArray) then
  283. begin
  284. value := methToArray.Invoke(aSrcObjList,[]);
  285. Assert(value.IsArray);
  286. rtype2 := ctx.GetType(aTgtObjList.ClassInfo);
  287. rProp := rtype2.GetProperty('List');
  288. typinfo := GetTypeData(rProp.PropertyType.Handle).DynArrElType^;
  289. for i := 0 to value.GetArrayLength - 1 do
  290. begin
  291. obj := typinfo.TypeData.ClassType.Create;
  292. TObjMapper.Map(value.GetArrayElement(i).AsObject,obj,aCustomMapping);
  293. TObjectList<TObject>(aTgtObjList).Add(obj);
  294. end;
  295. end;
  296. end;
  297. {$ELSE}
  298. begin
  299. end;
  300. {$ENDIF}
  301. end.