Quick.AutoMapper.pas 17 KB

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