Quick.AutoMapper.pas 16 KB

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