Quick.AutoMapper.pas 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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. interface
  23. uses
  24. SysUtils,
  25. Generics.Collections,
  26. {$IFDEF FPC}
  27. typinfo,
  28. {$ENDIF}
  29. RTTI;
  30. type
  31. TCustomMapping = class
  32. private
  33. fMapDictionary : TDictionary<string,string>;
  34. public
  35. constructor Create;
  36. destructor Destroy; override;
  37. procedure AddMap(const aName, aMapName : string);
  38. function GetMap(const aName : string; out vMapName : string) : Boolean;
  39. end;
  40. TObjMapper = class
  41. public
  42. class procedure Map(aSrcObj : TObject; aTgtObj : TObject; aCustomMapping: TCustomMapping = nil);
  43. end;
  44. TMapper<T : class, constructor> = class
  45. public
  46. class function Map(aSrcObj : TObject; aCustomMapping: TCustomMapping = nil): T; overload;
  47. class procedure Map(aSrcObj : TObject; aTgtObj : T; aCustomMapping : TCustomMapping = nil); overload;
  48. end;
  49. TAutoMapper<TClass1, TClass2 : class, constructor> = class
  50. private
  51. fCustomMapping : TCustomMapping;
  52. public
  53. constructor Create;
  54. destructor Destroy; override;
  55. property CustomMapping : TCustomMapping read fCustomMapping write fCustomMapping;
  56. function Map(aSrcObj : TClass1) : TClass2; overload;
  57. {$IFNDEF FPC}
  58. function Map(aSrcObj : TClass2) : TClass1; overload;
  59. {$ELSE}
  60. //freepascal detects overload with generic types as duplicated function, added dummy field to avoid this
  61. function Map(aSrcObj : TClass2; dummy : Boolean = True) : TClass1; overload;
  62. {$ENDIF}
  63. end;
  64. EAutoMapperError = class(Exception);
  65. implementation
  66. { TObjMapper }
  67. class procedure TObjMapper.Map(aSrcObj : TObject; aTgtObj : TObject; aCustomMapping: TCustomMapping = nil);
  68. var
  69. ctx : TRttiContext;
  70. rType : TRttiType;
  71. tgtprop : TRttiProperty;
  72. mapname : string;
  73. obj : TObject;
  74. begin
  75. if aTgtObj = nil then aTgtObj := aTgtObj.ClassType.Create;
  76. for tgtprop in ctx.GetType(aTgtObj.ClassInfo).GetProperties do
  77. begin
  78. if tgtprop.IsWritable then
  79. begin
  80. if not tgtprop.PropertyType.IsInstance then
  81. begin
  82. rType := ctx.GetType(aSrcObj.ClassInfo);
  83. if Assigned(aCustomMapping) then
  84. begin
  85. if aCustomMapping.GetMap(tgtprop.Name,mapname) then
  86. begin
  87. if rType.GetProperty(mapname) = nil then raise EAutoMapperError.CreateFmt('No valid custom mapping (Source: %s - Target: %s)',[mapname,tgtprop.Name]);
  88. {$IFNDEF FPC}
  89. tgtprop.SetValue(aTgtObj,rType.GetProperty(mapname).GetValue(aSrcObj))
  90. {$ELSE}
  91. SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,mapname));
  92. {$ENDIF}
  93. end
  94. else
  95. begin
  96. if rType.GetProperty(tgtprop.Name) <> nil then
  97. try
  98. {$IFNDEF FPC}
  99. tgtprop.SetValue(aTgtObj,rType.GetProperty(tgtprop.Name).GetValue(aSrcObj));
  100. {$ELSE}
  101. SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,tgtprop.Name));
  102. {$ENDIF}
  103. except
  104. on E : Exception do raise EAUtoMapperError.CreateFmt('Error mapping property "%s" : %s',[tgtprop.Name,e.message]);
  105. end;
  106. end;
  107. end
  108. else
  109. begin
  110. try
  111. {$IFNDEF FPC}
  112. if rType.GetProperty(tgtprop.Name) <> nil then tgtprop.SetValue(aTgtObj,rType.GetProperty(tgtprop.Name).GetValue(aSrcObj));
  113. {$ELSE}
  114. if rType.GetProperty(tgtprop.Name) <> nil then SetPropValue(aTgtObj,tgtprop.Name,GetPropValue(aSrcObj,tgtprop.Name));
  115. {$ENDIF}
  116. except
  117. on E : Exception do raise EAUtoMapperError.CreateFmt('Error mapping property "%s" : %s',[tgtprop.Name,e.message]);
  118. end;
  119. end;
  120. end
  121. else
  122. begin
  123. obj := tgtprop.GetValue(aTgtObj).AsObject;
  124. {$IFNDEF FPC}
  125. if obj = nil then obj := TObject.Create;
  126. {$ELSE}
  127. if obj = nil then obj := GetObjectProp(aSrcObj,tgtprop.Name).ClassType.Create;
  128. {$ENDIF}
  129. if obj <> nil then
  130. begin
  131. {$IFNDEF FPC}
  132. TObjMapper.Map(rType.GetProperty(tgtprop.Name).GetValue(aSrcObj).AsObject,obj,aCustomMapping);
  133. {$ELSE}
  134. TObjMapper.Map(GetObjectProp(aSrcObj,tgtprop.Name),obj,aCustomMapping);
  135. SetObjectProp(aTgtObj,tgtprop.Name,obj);
  136. {$ENDIF}
  137. end
  138. else raise EAutoMapperError.CreateFmt('Target object "%s" not autocreated by class',[tgtprop.Name]);
  139. end;
  140. end;
  141. end;
  142. end;
  143. class function TMapper<T>.Map(aSrcObj : TObject; aCustomMapping: TCustomMapping = nil) : T;
  144. var
  145. obj : T;
  146. begin
  147. obj := T.Create;
  148. TObjMapper.Map(aSrcObj,obj,aCustomMapping);
  149. Result := obj;
  150. end;
  151. class procedure TMapper<T>.Map(aSrcObj : TObject; aTgtObj : T; aCustomMapping : TCustomMapping = nil);
  152. begin
  153. TObjMapper.Map(aSrcObj, aTgtObj, aCustomMapping);
  154. end;
  155. { TAutoMapper<TClass1, TClass2> }
  156. constructor TAutoMapper<TClass1, TClass2>.Create;
  157. begin
  158. fCustomMapping := TCustomMapping.Create;
  159. end;
  160. destructor TAutoMapper<TClass1, TClass2>.Destroy;
  161. begin
  162. if Assigned(fCustomMapping) then fCustomMapping.Free;
  163. inherited;
  164. end;
  165. {}
  166. function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass1): TClass2;
  167. begin
  168. Result := TMapper<TClass2>.Map(aSrcObj,fCustomMapping);
  169. end;
  170. {$IFNDEF FPC}
  171. function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass2): TClass1;
  172. {$ELSE}
  173. function TAutoMapper<TClass1, TClass2>.Map(aSrcObj: TClass2; dummy : Boolean = True): TClass1;
  174. {$ENDIF}
  175. begin
  176. Result := TMapper<TClass1>.Map(aSrcObj,fCustomMapping);
  177. end;
  178. { TCustomMappingFields }
  179. procedure TCustomMapping.AddMap(const aName, aMapName: string);
  180. begin
  181. //add map fields
  182. fMapDictionary.Add(aName,aMapName);
  183. //add reverse lookup
  184. fMapDictionary.Add(aMapName,aName);
  185. end;
  186. constructor TCustomMapping.Create;
  187. begin
  188. fMapDictionary := TDictionary<string,string>.Create;
  189. end;
  190. destructor TCustomMapping.Destroy;
  191. begin
  192. fMapDictionary.Free;
  193. inherited;
  194. end;
  195. function TCustomMapping.GetMap(const aName: string; out vMapName: string): Boolean;
  196. begin
  197. Result := fMapDictionary.TryGetValue(aName,vMapName);
  198. end;
  199. end.