Quick.AutoMapper.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  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.2
  7. Created : 25/08/2018
  8. Modified : 31/03/2019
  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. typinfo,
  28. Quick.Value,
  29. {$IFDEF FPC}
  30. Variants,
  31. {$ENDIF}
  32. RTTI;
  33. type
  34. {$IFNDEF FPC}
  35. TFlexValue = TValue;
  36. {$ELSE}
  37. TFlexValue = variant;
  38. {$ENDIF}
  39. {$IFNDEF FPC}
  40. TMappingProc<TClass1> = reference to procedure(const aSrcObj : TClass1; const aTargetName : string; out Value : TFlexValue);
  41. TAfterMappingProc<TClass1,TClass2> = reference to procedure(const aSrcObj : TClass1; aTgtObj : TClass2);
  42. {$ELSE}
  43. TMappingProc<TObject> = procedure(const aSrcObj : TObject; const aTargetName : string; out Value : TFlexValue) of object;
  44. TAfterMappingProc<TClass1,TClass2> = procedure(const aSrcObj : TClass1; aTgtObj : TClass2) of object;
  45. {$ENDIF}
  46. TCustomMapping = class
  47. private
  48. fMapDictionary : TDictionary<string,string>;
  49. public
  50. constructor Create;
  51. destructor Destroy; override;
  52. procedure AddMap(const aName, aMapName : string);
  53. function GetMap(const aName : string; out vMapName : string) : Boolean;
  54. end;
  55. TObjMapper = class
  56. public
  57. class procedure Map(aSrcObj : TObject; aTgtObj : TObject; aCustomMapping: TCustomMapping = nil); overload;
  58. {$IFNDEF FPC}
  59. class procedure Map<Tm>(aSrcObj : TObject; aTgtObj : TObject; aDoMappingProc : TMappingProc<Tm>; aCustomMapping: TCustomMapping = nil); overload;
  60. {$ELSE}
  61. class procedure Map(aSrcObj : TObject; aTgtObj : TObject; aDoMappingProc : TMappingProc<TObject>; aCustomMapping: TCustomMapping = nil); overload;
  62. {$ENDIF}
  63. end;
  64. TListMapper = class
  65. public
  66. class procedure Map(aSrcList, aTgtList: TObject; aCustomMapping: TCustomMapping);
  67. end;
  68. TObjListMapper = class
  69. public
  70. class procedure Map(aSrcObjList : TObject; aTgtObjList : TObject; aCustomMapping : TCustomMapping = nil); overload;
  71. {$IFNDEF FPC}
  72. class procedure Map<Tm>(aSrcObjList : TObject; aTgtObjList : TObject; aDoMappingProc : TMappingProc<Tm>; aCustomMapping : TCustomMapping = nil); overload;
  73. {$ELSE}
  74. class procedure Map(aSrcObjList : TObject; aTgtObjList : TObject; aDoMappingProc : TMappingProc<TObject>; aCustomMapping : TCustomMapping = nil); overload;
  75. {$ENDIF}
  76. end;
  77. TMapper<T : class, constructor> = class
  78. public
  79. class function Map(aSrcObj : TObject; aCustomMapping: TCustomMapping = nil): T; overload;
  80. class procedure Map(aSrcObj : TObject; aTgtObj : T; aCustomMapping : TCustomMapping = nil); overload;
  81. {$IFNDEF FPC}
  82. class function Map<Tm>(aSrcObj : TObject; aDoMappingProc : TMappingProc<Tm>; aCustomMapping: TCustomMapping = nil): T; overload;
  83. class procedure Map<Tm>(aSrcObj : TObject; aTgtObj : T; aDoMappingProc : TMappingProc<Tm>; aCustomMapping: TCustomMapping = nil); overload;
  84. {$ELSE}
  85. class function Map(aSrcObj : TObject; aDoMappingProc : TMappingProc<TObject>; aCustomMapping: TCustomMapping = nil): T; overload;
  86. class procedure Map(aSrcObj : TObject; aTgtObj : T; aDoMappingProc : TMappingProc<TObject>; aCustomMapping: TCustomMapping);
  87. {$ENDIF}
  88. end;
  89. TAutoMapper<TClass1, TClass2 : class, constructor> = class
  90. private
  91. fCustomMapping : TCustomMapping;
  92. {$IFNDEF FPC}
  93. fOnDoMapping : TMappingProc<TClass1>;
  94. {$ELSE}
  95. fOnDoMapping : TMappingProc<TObject>;
  96. {$ENDIF}
  97. fOnAfterMapping : TAfterMappingProc<TClass1,TClass2>;
  98. public
  99. constructor Create;
  100. destructor Destroy; override;
  101. property CustomMapping : TCustomMapping read fCustomMapping write fCustomMapping;
  102. {$IFNDEF FPC}
  103. property OnDoMapping : TMappingProc<TClass1> read fOnDoMapping write fOnDoMapping;
  104. {$ELSE}
  105. property OnDoMapping : TMappingProc<TObject> read fOnDoMapping write fOnDoMapping;
  106. {$ENDIF}
  107. property OnAfterMapping : TAfterMappingProc<TClass1,TClass2> read fOnAfterMapping write fOnAfterMapping;
  108. function Map(aSrcObj : TClass1) : TClass2; overload;
  109. {$IFNDEF FPC}
  110. function Map(aSrcObj : TClass2) : TClass1; overload;
  111. {$ELSE}
  112. //freepascal detects overload with generic types as duplicated function, added dummy field to avoid this
  113. function Map(aSrcObj : TClass2; dummy : Boolean = True) : TClass1; overload;
  114. {$ENDIF}
  115. end;
  116. EAutoMapperError = class(Exception);
  117. implementation
  118. { TObjMapper }
  119. class procedure TObjMapper.Map(aSrcObj : TObject; aTgtObj : TObject; aCustomMapping: TCustomMapping = nil);
  120. begin
  121. Map{$IFNDEF FPC}<TObject>{$ENDIF}(aSrcObj,aTgtObj,nil,aCustomMapping);
  122. end;
  123. {$IFNDEF FPC}
  124. class procedure TObjMapper.Map<Tm>(aSrcObj : TObject; aTgtObj : TObject; aDoMappingProc : TMappingProc<Tm>; aCustomMapping: TCustomMapping = nil);
  125. {$ELSE}
  126. class procedure TObjMapper.Map(aSrcObj : TObject; aTgtObj : TObject; aDoMappingProc : TMappingProc<TObject>; aCustomMapping: TCustomMapping = nil);
  127. {$ENDIF}
  128. var
  129. ctx : TRttiContext;
  130. rType : TRttiType;
  131. tgtprop : TRttiProperty;
  132. mapname : string;
  133. obj : TObject;
  134. manualmapping : Boolean;
  135. value : TFlexValue;
  136. {$IFNDEF FPC}
  137. clname : string;
  138. objvalue : TValue;
  139. {$ENDIF}
  140. begin
  141. //if aTgtObj = nil then aTgtObj := GetTypeData(aTgtObj.ClassInfo).classType.Create;
  142. if aTgtObj = nil then raise EAutoMapperError.Create('TObjMapper: Target Object passed must be created before');
  143. {$IFNDEF FPC}
  144. objvalue := TValue.From(aSrcObj);
  145. {$ENDIF}
  146. rType := ctx.GetType(aSrcObj.ClassInfo);
  147. for tgtprop in ctx.GetType(aTgtObj.ClassInfo).GetProperties do
  148. begin
  149. if tgtprop.IsWritable then
  150. begin
  151. if not tgtprop.PropertyType.IsInstance then
  152. begin
  153. if Assigned(aCustomMapping) and (not Assigned(aDoMappingProc)) then
  154. begin
  155. if aCustomMapping.GetMap(tgtprop.Name,mapname) then
  156. begin
  157. if rType.GetProperty(mapname) = nil then raise EAutoMapperError.CreateFmt('No valid custom mapping (Source: %s - Target: %s)',[mapname,tgtprop.Name]);
  158. {$IFNDEF FPC}
  159. tgtprop.SetValue(aTgtObj,rType.GetProperty(mapname).GetValue(aSrcObj))
  160. {$ELSE}
  161. SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,mapname));
  162. {$ENDIF}
  163. end
  164. else
  165. begin
  166. if rType.GetProperty(tgtprop.Name) <> nil then
  167. try
  168. {$IFNDEF FPC}
  169. tgtprop.SetValue(aTgtObj,rType.GetProperty(tgtprop.Name).GetValue(aSrcObj));
  170. {$ELSE}
  171. SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,tgtprop.Name));
  172. {$ENDIF}
  173. except
  174. on E : Exception do raise EAUtoMapperError.CreateFmt('Error mapping property "%s" : %s',[tgtprop.Name,e.message]);
  175. end;
  176. end;
  177. end
  178. else
  179. begin
  180. try
  181. if Assigned(aDoMappingProc) then
  182. begin
  183. {$IFNDEF FPC}
  184. aDoMappingProc(objvalue.AsType<Tm>,tgtprop.Name,value);
  185. manualmapping := not value.IsEmpty;
  186. {$ELSE}
  187. aDoMappingProc(aSrcObj,tgtprop.Name,value);
  188. manualmapping := not varType(value) = varEmpty;
  189. {$ENDIF}
  190. end
  191. else manualmapping := False;
  192. if manualmapping then
  193. begin
  194. {$IFNDEF FPC}
  195. tgtprop.SetValue(aTgtObj,value);
  196. {$ELSE}
  197. SetPropValue(aTgtObj,tgtprop.Name,value);
  198. {$ENDIF}
  199. end
  200. else
  201. begin
  202. {$IFNDEF FPC}
  203. if rType.GetProperty(tgtprop.Name) <> nil then tgtprop.SetValue(aTgtObj,rType.GetProperty(tgtprop.Name).GetValue(aSrcObj));
  204. {$ELSE}
  205. if rType.GetProperty(tgtprop.Name) <> nil then SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,tgtprop.Name));
  206. {$ENDIF}
  207. end;
  208. except
  209. on E : Exception do raise EAUtoMapperError.CreateFmt('Error mapping property "%s" : %s',[tgtprop.Name,e.message]);
  210. end;
  211. end;
  212. end
  213. else
  214. begin
  215. obj := tgtprop.GetValue(aTgtObj).AsObject;
  216. {$IFNDEF FPC}
  217. if obj = nil then obj := TObject.Create;
  218. {$ELSE}
  219. if obj = nil then obj := GetObjectProp(aSrcObj,tgtprop.Name).ClassType.Create;
  220. {$ENDIF}
  221. if obj <> nil then
  222. begin
  223. {$IFNDEF FPC}
  224. clname := rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject.ClassName;
  225. if clname.StartsWith('TObjectList') then TObjListMapper.Map(rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject,obj,aCustomMapping)
  226. else TObjMapper.Map(rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject,obj,aCustomMapping)
  227. {$ELSE}
  228. TObjMapper.Map(GetObjectProp(aSrcObj,tgtprop.Name),obj,aCustomMapping);
  229. SetObjectProp(aTgtObj,tgtprop.Name,obj);
  230. {$ENDIF}
  231. end
  232. else raise EAutoMapperError.CreateFmt('Target object "%s" not autocreated by class',[tgtprop.Name]);
  233. end;
  234. end;
  235. end;
  236. end;
  237. class function TMapper<T>.Map(aSrcObj : TObject; aCustomMapping: TCustomMapping = nil) : T;
  238. begin
  239. Result := Map{$IFNDEF FPC}<TObject>{$ENDIF}(aSrcObj,nil,aCustomMapping);
  240. end;
  241. {$IFNDEF FPC}
  242. class function TMapper<T>.Map<Tm>(aSrcObj : TObject; aDoMappingProc : TMappingProc<Tm>; aCustomMapping: TCustomMapping = nil): T;
  243. {$ELSE}
  244. class function TMapper<T>.Map(aSrcObj : TObject; aDoMappingProc : TMappingProc<TObject>; aCustomMapping: TCustomMapping = nil): T;
  245. {$ENDIF}
  246. var
  247. obj : T;
  248. begin
  249. obj := T.Create;
  250. {$IFNDEF FPC}
  251. TObjMapper.Map<Tm>(aSrcObj,obj,aDoMappingProc,aCustomMapping);
  252. {$ELSE}
  253. TObjMapper.Map(aSrcObj,obj,aDoMappingProc,aCustomMapping);
  254. {$ENDIF}
  255. Result := obj;
  256. end;
  257. class procedure TMapper<T>.Map(aSrcObj : TObject; aTgtObj : T; aCustomMapping : TCustomMapping = nil);
  258. begin
  259. {$IFNDEF FPC}
  260. Map<T>(aSrcObj,aTgtObj,nil,aCustomMapping);
  261. {$ELSE}
  262. Map(aSrcObj,aTgtObj,nil,aCustomMapping);
  263. {$ENDIF}
  264. end;
  265. {$IFNDEF FPC}
  266. class procedure TMapper<T>.Map<Tm>(aSrcObj : TObject; aTgtObj : T; aDoMappingProc : TMappingProc<Tm>; aCustomMapping : TCustomMapping = nil);
  267. {$ELSE}
  268. class procedure TMapper<T>.Map(aSrcObj : TObject; aTgtObj : T; aDoMappingProc : TMappingProc<TObject>; aCustomMapping : TCustomMapping);
  269. {$ENDIF}
  270. begin
  271. {$IFNDEF FPC}
  272. TObjMapper.Map<Tm>(aSrcObj, aTgtObj, aDoMappingProc, aCustomMapping);
  273. {$ELSE}
  274. TObjMapper.Map(aSrcObj, aTgtObj, aDoMappingProc, aCustomMapping);
  275. {$ENDIF}
  276. end;
  277. { TAutoMapper<TClass1, TClass2> }
  278. constructor TAutoMapper<TClass1, TClass2>.Create;
  279. begin
  280. fCustomMapping := TCustomMapping.Create;
  281. fOnDoMapping := nil;
  282. fOnAfterMapping := nil;
  283. end;
  284. destructor TAutoMapper<TClass1, TClass2>.Destroy;
  285. begin
  286. if Assigned(fCustomMapping) then fCustomMapping.Free;
  287. fOnDoMapping := nil;
  288. fOnAfterMapping := nil;
  289. inherited;
  290. end;
  291. function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass1): TClass2;
  292. var
  293. objvalue : TValue;
  294. obj : TObject;
  295. begin
  296. obj := aSrcObj as TObject;
  297. //objvalue := TValue.From(aSrcObj).AsObject;
  298. {$IFNDEF FPC}
  299. Result := TMapper<TClass2>.Map<TClass1>(obj,fOnDoMapping,fCustomMapping);
  300. {$ELSE}
  301. Result := TMapper<TClass2>.Map(obj,fOnDoMapping,fCustomMapping);
  302. {$ENDIF}
  303. if Assigned(fOnAfterMapping) then fOnAfterMapping(aSrcObj,Result);
  304. end;
  305. {$IFNDEF FPC}
  306. function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass2): TClass1;
  307. begin
  308. Result := TMapper<TClass1>.Map<TClass1>(aSrcObj,fOnDoMapping,fCustomMapping);
  309. end;
  310. {$ELSE}
  311. function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass2; dummy : Boolean = True): TClass1;
  312. begin
  313. Result := TMapper<TClass1>.Map(aSrcObj,fOnDoMapping,fCustomMapping);
  314. end;
  315. {$ENDIF}
  316. { TCustomMappingFields }
  317. procedure TCustomMapping.AddMap(const aName, aMapName: string);
  318. begin
  319. //add map fields
  320. fMapDictionary.Add(aName,aMapName);
  321. //add reverse lookup
  322. fMapDictionary.Add(aMapName,aName);
  323. end;
  324. constructor TCustomMapping.Create;
  325. begin
  326. fMapDictionary := TDictionary<string,string>.Create;
  327. end;
  328. destructor TCustomMapping.Destroy;
  329. begin
  330. fMapDictionary.Free;
  331. inherited;
  332. end;
  333. function TCustomMapping.GetMap(const aName: string; out vMapName: string): Boolean;
  334. begin
  335. Result := fMapDictionary.TryGetValue(aName,vMapName);
  336. end;
  337. { TListMapper }
  338. class procedure TListMapper.Map(aSrcList, aTgtList: TObject; aCustomMapping: TCustomMapping);
  339. {$IFNDEF FPC}
  340. var
  341. rtype: TRttiType;
  342. rtype2 : TRttiType;
  343. typinfo : PTypeInfo;
  344. methToArray: TRttiMethod;
  345. value: TValue;
  346. valuecop : TValue;
  347. obj : TObject;
  348. i : Integer;
  349. rprop : TRttiProperty;
  350. ctx : TRttiContext;
  351. begin
  352. rtype := ctx.GetType(aSrcList.ClassInfo);
  353. methToArray := rtype.GetMethod('ToArray');
  354. if Assigned(methToArray) then
  355. begin
  356. value := methToArray.Invoke(aSrcList,[]);
  357. Assert(value.IsArray);
  358. rtype2 := ctx.GetType(aTgtList.ClassInfo);
  359. rProp := rtype2.GetProperty('List');
  360. typinfo := GetTypeData(rProp.PropertyType.Handle).DynArrElType^;
  361. for i := 0 to value.GetArrayLength - 1 do
  362. begin
  363. if typinfo.Kind = tkClass then
  364. begin
  365. obj := typinfo.TypeData.ClassType.Create;
  366. TObjMapper.Map(value.GetArrayElement(i).AsObject,obj,aCustomMapping);
  367. TList<TObject>(aTgtList).Add(obj);
  368. end
  369. else
  370. begin
  371. valuecop := value.GetArrayElement(i);
  372. case typinfo.Kind of
  373. tkChar, tkString, tkWChar, tkWString : TList<string>(aTgtList).Add(valuecop.AsString);
  374. tkInteger, tkInt64 : TList<Integer>(aTgtList).Add(valuecop.AsInt64);
  375. tkFloat : TList<Extended>(aTgtList).Add(valuecop.AsExtended);
  376. end;
  377. end;
  378. end;
  379. end;
  380. end;
  381. {$ELSE}
  382. begin
  383. end;
  384. {$ENDIF}
  385. { TObjListMapper }
  386. class procedure TObjListMapper.Map(aSrcObjList, aTgtObjList: TObject; aCustomMapping: TCustomMapping);
  387. begin
  388. {$IFNDEF FPC}
  389. Map<TObject>(aSrcObjList,aTgtObjList,nil,aCustomMapping);
  390. {$ELSE}
  391. Map(aSrcObjList,aTgtObjList,nil,aCustomMapping);
  392. {$ENDIF}
  393. end;
  394. {$IFNDEF FPC}
  395. class procedure TObjListMapper.Map<Tm>(aSrcObjList : TObject; aTgtObjList : TObject; aDoMappingProc : TMappingProc<Tm>; aCustomMapping : TCustomMapping = nil);
  396. var
  397. rtype: TRttiType;
  398. rtype2 : TRttiType;
  399. typinfo : PTypeInfo;
  400. methToArray: TRttiMethod;
  401. value: TValue;
  402. obj : TObject;
  403. i : Integer;
  404. rprop : TRttiProperty;
  405. ctx : TRttiContext;
  406. begin
  407. rtype := ctx.GetType(aSrcObjList.ClassInfo);
  408. methToArray := rtype.GetMethod('ToArray');
  409. if Assigned(methToArray) then
  410. begin
  411. value := methToArray.Invoke(aSrcObjList,[]);
  412. Assert(value.IsArray);
  413. rtype2 := ctx.GetType(aTgtObjList.ClassInfo);
  414. rProp := rtype2.GetProperty('List');
  415. typinfo := GetTypeData(rProp.PropertyType.Handle).DynArrElType^;
  416. for i := 0 to value.GetArrayLength - 1 do
  417. begin
  418. obj := typinfo.TypeData.ClassType.Create;
  419. TObjMapper.Map<Tm>(value.GetArrayElement(i).AsObject,obj,aDoMappingProc,aCustomMapping);
  420. TObjectList<TObject>(aTgtObjList).Add(obj);
  421. end;
  422. end;
  423. end;
  424. {$ELSE}
  425. class procedure TObjListMapper.Map(aSrcObjList : TObject; aTgtObjList : TObject; aDoMappingProc : TMappingProc<TObject>; aCustomMapping : TCustomMapping = nil);
  426. begin
  427. end;
  428. {$ENDIF}
  429. end.