Quick.Json.Serializer.pas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. { ***************************************************************************
  2. Copyright (c) 2015-2018 Kike Pérez
  3. Unit : Quick.JSON.Serializer
  4. Description : Json Serializer
  5. Author : Kike Pérez
  6. Version : 1.0
  7. Created : 21/05/2018
  8. Modified : 08/06/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.Json.Serializer;
  22. interface
  23. uses
  24. System.Classes,
  25. System.SysUtils,
  26. System.Rtti,
  27. System.TypInfo,
  28. System.Json,
  29. System.DateUtils,
  30. Quick.Commons;
  31. type
  32. EJsonSerializeError = class(Exception)
  33. end;
  34. TNotSerializableProperty = class(TCustomAttribute);
  35. TCommentProperty = class(TCustomAttribute)
  36. private
  37. fComment : string;
  38. public
  39. constructor Create(const aComment: string);
  40. property Comment : string read fComment;
  41. end;
  42. TCustomNameProperty = class(TCustomAttribute)
  43. private
  44. fName : string;
  45. public
  46. constructor Create(const aName: string);
  47. property Name : string read fName;
  48. end;
  49. IJsonSerializer = interface
  50. ['{CA26F7AE-F1FE-41BE-9C23-723A687F60D1}']
  51. function JsonToObject(aType: TClass; const aJson: string): TObject; overload;
  52. function JsonToObject(aObject: TObject; const aJson: string): TObject; overload;
  53. function ObjectToJson(aObject: TObject): string;
  54. end;
  55. TJsonSerializer = class(TInterfacedObject,IJsonSerializer)
  56. strict private
  57. function GetValue(aAddr: Pointer; aType: TRTTIType): TValue;
  58. function IsAllowedProperty(aObject : TObject; const aPropertyName : string) : Boolean;
  59. procedure DeserializeDynArray(aProperty : TRttiProperty; aObject : TObject; const aJsonArray: TJSONArray);
  60. function DeserializeRecord(aRecord : TValue; aObject : TObject; const aJson : TJSONObject) : TValue;
  61. function DeserializeClass(aType : TClass; const aJson : TJSONObject) : TObject;
  62. function DeserializeObject(aObject : TObject; const aJson : TJSONObject) : TObject; overload;
  63. function DeserializeProperty(aObject : TObject; const aName : string; aProperty : TRttiProperty; const aJson : TJSONObject) : TObject; overload;
  64. function DeserializeType(aObject : TObject; aType : TTypeKind; aTypeInfo : PTypeInfo; const aValue: string) : TValue;
  65. function Serialize(const aName : string; aValue : TValue) : TJSONPair; overload;
  66. function Serialize(aObject : TObject) : TJSONObject; overload;
  67. public
  68. function JsonToObject(aType : TClass; const aJson: string) : TObject; overload;
  69. function JsonToObject(aObject : TObject; const aJson: string) : TObject; overload;
  70. function ObjectToJson(aObject : TObject): string;
  71. end;
  72. PPByte = ^PByte;
  73. resourcestring
  74. cNotSupportedDataType = 'Not supported "%s" data type "%s"';
  75. cNotSerializable = 'Object is not serializable';
  76. implementation
  77. { TqlJsonSerializer }
  78. procedure TJsonSerializer.DeserializeDynArray(aProperty: TRttiProperty; aObject: TObject; const aJsonArray: TJSONArray);
  79. var
  80. rType: PTypeInfo;
  81. len: NativeInt;
  82. pArr: Pointer;
  83. rValue : TValue;
  84. rItemValue: TValue;
  85. i: Integer;
  86. objClass: TClass;
  87. ctx : TRttiContext;
  88. rRec : TRttiRecordType;
  89. json : TJSONObject;
  90. rDynArray : TRttiDynamicArrayType;
  91. propObj : TObject;
  92. begin
  93. if GetTypeData(aProperty.PropertyType.Handle).DynArrElType = nil then Exit;
  94. len := aJsonArray.Count;
  95. rType := GetTypeData(aProperty.PropertyType.Handle).DynArrElType^;
  96. pArr := nil;
  97. DynArraySetLength(pArr, aProperty.PropertyType.Handle, 1, @len);
  98. try
  99. TValue.Make(@pArr, aProperty.PropertyType.Handle, rValue);
  100. rDynArray := ctx.GetType(rValue.TypeInfo) as TRTTIDynamicArrayType;
  101. for i := 0 to aJsonArray.Count - 1 do
  102. begin
  103. rItemValue := nil;
  104. case rType.Kind of
  105. tkClass :
  106. begin
  107. if aJsonArray.Items[i] is TJSONObject then
  108. begin
  109. propObj := GetValue(PPByte(rValue.GetReferenceToRawData)^ +rDynArray.ElementType.TypeSize * i, rDynArray.ElementType).AsObject;
  110. if propObj = nil then
  111. begin
  112. objClass := rType.TypeData.ClassType;
  113. rItemValue := DeserializeClass(objClass, TJSONObject(aJsonArray.Items[i]));
  114. end
  115. else
  116. begin
  117. DeserializeObject(propObj,TJSONObject(aJsonArray.Items[i]));
  118. end;
  119. end;
  120. end;
  121. tkRecord :
  122. begin
  123. json := TJSONObject(aJsonArray.Items[i]);
  124. rItemValue := DeserializeRecord(GetValue(PPByte(rValue.GetReferenceToRawData)^ +rDynArray.ElementType.TypeSize * i,
  125. rDynArray.ElementType),aObject,json);
  126. end;
  127. tkMethod, tkPointer, tkClassRef ,tkInterface, tkProcedure :
  128. begin
  129. //skip these properties
  130. end
  131. else
  132. begin
  133. //raise EJsonSerializeError.Create(Format(cNotSupportedDataType,[aProperty.Name,GetTypeName(rType)]));
  134. rItemValue := DeserializeType(aObject,rType.Kind,aProperty.GetValue(aObject).TypeInfo,aJsonArray.Items[i].Value);
  135. end;
  136. end;
  137. if not rItemValue.IsEmpty then rValue.SetArrayElement(i,rItemValue);
  138. end;
  139. aProperty.SetValue(aObject,rValue);
  140. finally
  141. DynArrayClear(pArr, aProperty.PropertyType.Handle);
  142. end;
  143. end;
  144. function TJsonSerializer.DeserializeRecord(aRecord : TValue; aObject : TObject; const aJson : TJSONObject) : TValue;
  145. var
  146. ctx : TRttiContext;
  147. rRec : TRttiRecordType;
  148. rField : TRttiField;
  149. rValue : TValue;
  150. member : TJSONPair;
  151. json : TJSONObject;
  152. objClass : TClass;
  153. propobj : TObject;
  154. begin
  155. rRec := ctx.GetType(aRecord.TypeInfo).AsRecord;
  156. try
  157. for rField in rRec.GetFields do
  158. begin
  159. rValue := nil;
  160. member := TJSONPair(aJson.GetValue(rField.Name));
  161. if member <> nil then
  162. case rField.FieldType.TypeKind of
  163. tkDynArray :
  164. begin
  165. {jArray := TJSONObject.ParseJSONValue(member.ToJSON) as TJSONArray;
  166. try
  167. DeserializeDynArray(aProp,Result,jArray);
  168. finally
  169. jArray.Free;
  170. end;}
  171. end;
  172. tkClass :
  173. begin
  174. //if (member.JsonValue is TJSONObject) then
  175. begin
  176. propobj := rField.GetValue(@aRecord).AsObject;
  177. json := TJSONObject.ParseJSONValue(member.ToJson) as TJSONObject;
  178. try
  179. if propobj = nil then
  180. begin
  181. objClass := rField.FieldType.Handle^.TypeData.ClassType;// aProperty.PropertyType.Handle^.TypeData.ClassType;
  182. rValue := DeserializeClass(objClass,json);
  183. end
  184. else
  185. begin
  186. DeserializeObject(propobj,json);
  187. end;
  188. finally
  189. json.Free;
  190. end;
  191. end
  192. end;
  193. else
  194. begin
  195. rValue := DeserializeType(aObject,rField.FieldType.TypeKind,rField.FieldType.Handle,member.JsonString.ToString);
  196. end;
  197. end;
  198. if not rValue.IsEmpty then rField.SetValue(aRecord.GetReferenceToRawData,rValue);
  199. end;
  200. Result := aRecord;
  201. finally
  202. ctx.Free;
  203. end;
  204. end;
  205. function TJsonSerializer.JsonToObject(aObject: TObject; const aJson: string): TObject;
  206. var
  207. json: TJSONObject;
  208. begin
  209. json := TJSONObject.ParseJSONValue(aJson,True) as TJSONObject;
  210. try
  211. Result := DeserializeObject(aObject,json);
  212. finally
  213. json.Free;
  214. end;
  215. end;
  216. function TJsonSerializer.JsonToObject(aType: TClass; const aJson: string): TObject;
  217. var
  218. json: TJSONObject;
  219. begin
  220. json := TJSONObject.ParseJSONValue(aJson) as TJSONObject;
  221. try
  222. Result := DeserializeClass(aType,json);
  223. finally
  224. json.Free;
  225. end;
  226. end;
  227. function TJsonSerializer.ObjectToJson(aObject: TObject): string;
  228. var
  229. json: TJSONObject;
  230. begin
  231. json := Serialize(aObject);
  232. try
  233. Result := json.ToJSON;
  234. finally
  235. json.Free;
  236. end;
  237. end;
  238. function TJsonSerializer.DeserializeClass(aType: TClass; const aJson: TJSONObject): TObject;
  239. begin
  240. Result := nil;
  241. if aJson.Count = 0 then Exit;
  242. Result := aType.Create;
  243. try
  244. Result := DeserializeObject(Result,aJson);
  245. except
  246. Result.Free;
  247. raise;
  248. end;
  249. end;
  250. function TJsonSerializer.DeserializeObject(aObject: TObject; const aJson: TJSONObject): TObject;
  251. var
  252. ctx: TRttiContext;
  253. rType: TRttiType;
  254. rProp: TRttiProperty;
  255. attr: TCustomAttribute;
  256. rValue: TValue;
  257. propertyname : string;
  258. begin
  259. Result := aObject;
  260. if (aJson.Count = 0) or (Result = nil) then Exit;
  261. try
  262. rType := ctx.GetType(aObject.ClassInfo);
  263. try
  264. for rProp in rType.GetProperties do
  265. begin
  266. if (rProp.PropertyType.IsPublicType) and (rProp.IsWritable) and (IsAllowedProperty(aObject,rProp.Name)) then
  267. begin
  268. propertyname := rProp.Name;
  269. for attr in rProp.GetAttributes do if attr is TCustomNameProperty then propertyname := TCustomNameProperty(attr).Name;
  270. Result := DeserializeProperty(Result, propertyname, rProp, aJson);
  271. end;
  272. end;
  273. finally
  274. ctx.Free;
  275. end;
  276. except
  277. Result.Free;
  278. raise;
  279. end;
  280. end;
  281. function TJsonSerializer.DeserializeProperty(aObject : TObject; const aName : string; aProperty : TRttiProperty; const aJson : TJSONObject) : TObject;
  282. var
  283. rType : PTypeInfo;
  284. ctx : TRttiContext;
  285. rValue : TValue;
  286. member : TJSONPair;
  287. NotSerializable : Boolean;
  288. objClass: TClass;
  289. jArray : TJSONArray;
  290. rRec : TRttiRecordType;
  291. rField : TRttiField;
  292. json : TJSONObject;
  293. begin
  294. Result := aObject;
  295. member := TJSONPair(aJson.GetValue(aName));
  296. if member <> nil then
  297. begin
  298. case aProperty.PropertyType.TypeKind of
  299. tkDynArray :
  300. begin
  301. jArray := TJSONObject.ParseJSONValue(member.ToJSON) as TJSONArray;
  302. try
  303. DeserializeDynArray(aProperty,Result,jArray);
  304. finally
  305. jArray.Free;
  306. end;
  307. end;
  308. tkClass :
  309. begin
  310. //if (member.JsonValue is TJSONObject) then
  311. begin
  312. json := TJSONObject.ParseJSONValue(member.ToJson) as TJSONObject;
  313. try
  314. if aProperty.GetValue(aObject).AsObject = nil then
  315. begin
  316. objClass := aProperty.PropertyType.Handle^.TypeData.ClassType;
  317. rValue := DeserializeClass(objClass,json)
  318. end
  319. else
  320. begin
  321. rValue := DeserializeObject(aProperty.GetValue(aObject).AsObject,json);
  322. Exit;
  323. end;
  324. finally
  325. json.Free;
  326. end;
  327. end
  328. end;
  329. tkRecord :
  330. begin
  331. json := TJSONObject.ParseJSONValue(member.ToJson) as TJSONObject;
  332. try
  333. rValue := DeserializeRecord(aProperty.GetValue(aObject),aObject,json);
  334. finally
  335. json.Free;
  336. end;
  337. end;
  338. else
  339. begin
  340. rValue := DeserializeType(Result,aProperty.PropertyType.TypeKind,aProperty.GetValue(Result).TypeInfo,member.ToJSON);
  341. end;
  342. end;
  343. if not rValue.IsEmpty then aProperty.SetValue(Result,rValue);
  344. end;
  345. end;
  346. function TJsonSerializer.DeserializeType(aObject : TObject; aType : TTypeKind; aTypeInfo : PTypeInfo; const aValue: string) : TValue;
  347. var
  348. i : Integer;
  349. value : string;
  350. begin
  351. value := AnsiDequotedStr(aValue,'"');
  352. case aType of
  353. tkString, tkLString, tkWString, tkUString :
  354. begin
  355. Result := value;
  356. end;
  357. tkChar, tkWChar :
  358. begin
  359. Result := value;
  360. end;
  361. tkInteger :
  362. begin
  363. Result := StrToInt(value);
  364. end;
  365. tkInt64 :
  366. begin
  367. Result := StrToInt64(value);
  368. end;
  369. tkFloat :
  370. begin
  371. if aTypeInfo = TypeInfo(TDateTime) then
  372. begin
  373. Result := JsonDateToDateTime(value);
  374. end
  375. else if aTypeInfo = TypeInfo(TDate) then
  376. begin
  377. Result := StrToDate(value);
  378. end
  379. else if aTypeInfo = TypeInfo(TTime) then
  380. begin
  381. Result := StrToTime(value);
  382. end
  383. else
  384. begin
  385. Result := StrToFloat(value);
  386. end;
  387. end;
  388. tkEnumeration :
  389. begin
  390. if aTypeInfo = System.TypeInfo(Boolean) then
  391. begin
  392. Result := StrToBool(value);
  393. end
  394. else
  395. begin
  396. TValue.Make(GetEnumValue(aTypeInfo,value),aTypeInfo, Result);
  397. end;
  398. end;
  399. tkSet :
  400. begin
  401. i := StringToSet(aTypeInfo,value);
  402. TValue.Make(@i,aTypeInfo,Result);
  403. end;
  404. else
  405. begin
  406. //raise EclJsonSerializerError.Create('Not supported data type!');
  407. end;
  408. end;
  409. end;
  410. function TJsonSerializer.IsAllowedProperty(aObject : TObject; const aPropertyName : string) : Boolean;
  411. var
  412. propname : string;
  413. begin
  414. Result := True;
  415. propname := aPropertyName.ToLower;
  416. if (aObject.ClassName.StartsWith('TObjectList')) then
  417. begin
  418. if (propname = 'capacity') or (propname = 'count') or (propname = 'ownsobjects') then Result := False;
  419. end
  420. else if (propname = 'refcount') then Result := False;
  421. end;
  422. function TJsonSerializer.Serialize(aObject: TObject): TJSONObject;
  423. var
  424. ctx: TRttiContext;
  425. attr : TCustomAttribute;
  426. rType: TRttiType;
  427. rProp: TRttiProperty;
  428. jpair : TJSONPair;
  429. ExcludeSerialize : Boolean;
  430. comment : string;
  431. propertyname : string;
  432. begin
  433. if (aObject = nil) then
  434. begin
  435. Result := nil;
  436. Exit;
  437. end;
  438. Result := TJSONObject.Create;
  439. try
  440. rType := ctx.GetType(aObject.ClassInfo);
  441. try
  442. //s := rType.ToString;
  443. for rProp in rType.GetProperties do
  444. begin
  445. ExcludeSerialize := False;
  446. comment := '';
  447. propertyname := rProp.Name;
  448. for attr in rProp.GetAttributes do
  449. begin
  450. if attr is TNotSerializableProperty then ExcludeSerialize := True
  451. else if attr is TCommentProperty then comment := TCommentProperty(attr).Comment
  452. else if attr is TCustomNameProperty then propertyname := TCustomNameProperty(attr).Name;
  453. end;
  454. if (rProp.PropertyType.IsPublicType) and (IsAllowedProperty(aObject,propertyname)) and (not ExcludeSerialize) then
  455. begin
  456. //add comment as pair
  457. if comment <> '' then Result.AddPair(TJSONPair.Create('#Comment#->'+propertyname,Comment));
  458. //s := rProp.Name;
  459. jpair := Serialize(propertyname,rProp.GetValue(aObject));
  460. //s := jpair.JsonValue.ToString;
  461. if jpair <> nil then Result.AddPair(jpair)
  462. else jpair.Free;
  463. //Result.AddPair(Serialize(rProp.Name,rProp.GetValue(aObject)));
  464. //s := Result.ToJSON;
  465. end;
  466. end;
  467. finally
  468. ctx.Free;
  469. end;
  470. except
  471. Result.Free;
  472. raise;
  473. end;
  474. end;
  475. function TJsonSerializer.GetValue(aAddr: Pointer; aType: TRTTIType): TValue;
  476. begin
  477. TValue.Make(aAddr,aType.Handle,Result);
  478. end;
  479. function TJsonSerializer.Serialize(const aName : string; aValue : TValue): TJSONPair;
  480. var
  481. ctx: TRttiContext;
  482. rRec : TRttiRecordType;
  483. rField : TRttiField;
  484. rDynArray : TRTTIDynamicArrayType;
  485. json : TJSONObject;
  486. jArray : TJSONArray;
  487. jPair : TJSONPair;
  488. jValue : TJSONValue;
  489. i : Integer;
  490. begin
  491. Result := TJSONPair.Create(aName,nil);
  492. //Result.JsonString := TJSONString(aName);
  493. try
  494. case aValue.Kind of
  495. tkDynArray :
  496. begin
  497. jArray := TJSONArray.Create;
  498. rDynArray := ctx.GetType(aValue.TypeInfo) as TRTTIDynamicArrayType;
  499. try
  500. for i := 0 to aValue.GetArrayLength - 1 do
  501. begin
  502. jPair := Serialize(aName,GetValue(PPByte(aValue.GetReferenceToRawData)^ + rDynArray.ElementType.TypeSize * i, rDynArray.ElementType));
  503. try
  504. //jValue := TJsonValue(jPair.JsonValue.Clone);
  505. jValue := jPair.JsonValue;
  506. jArray.AddElement(jValue);
  507. jPair.JsonValue.Owned := False;
  508. finally
  509. jPair.Free;
  510. jValue.Owned := True;
  511. end;
  512. end;
  513. Result.JsonValue := jArray;
  514. finally
  515. ctx.Free;
  516. end;
  517. end;
  518. tkClass :
  519. begin
  520. Result.JsonValue := TJSONValue(Serialize(aValue.AsObject));
  521. end;
  522. tkString, tkLString, tkWString, tkUString :
  523. begin
  524. Result.JsonValue := TJSONString.Create(aValue.AsString);
  525. end;
  526. tkChar, tkWChar :
  527. begin
  528. Result.JsonValue := TJSONString.Create(aValue.AsString);
  529. end;
  530. tkInteger :
  531. begin
  532. Result.JsonValue := TJSONNumber.Create(aValue.AsInteger);
  533. end;
  534. tkInt64 :
  535. begin
  536. Result.JsonValue := TJSONNumber.Create(aValue.AsInt64);
  537. end;
  538. tkFloat :
  539. begin
  540. if aValue.TypeInfo = TypeInfo(TDateTime) then
  541. begin
  542. Result.JsonValue := TJSONString.Create(DateTimeToJsonDate(aValue.AsExtended));
  543. end
  544. else if aValue.TypeInfo = TypeInfo(TDate) then
  545. begin
  546. Result.JsonValue := TJSONString.Create(DateToStr(aValue.AsExtended));
  547. end
  548. else if aValue.TypeInfo = TypeInfo(TTime) then
  549. begin
  550. Result.JsonValue := TJSONString.Create(TimeToStr(aValue.AsExtended));
  551. end
  552. else Result.JsonValue := TJSONNumber.Create(aValue.AsExtended);
  553. end;
  554. tkEnumeration :
  555. begin
  556. if (aValue.TypeInfo = System.TypeInfo(Boolean)) then
  557. begin
  558. Result.JsonValue := TJSONBool.Create(aValue.AsBoolean);
  559. end
  560. else
  561. begin
  562. Result.JsonValue := TJSONString.Create(GetEnumName(aValue.TypeInfo,aValue.AsOrdinal));
  563. //Result.JsonValue := TJSONString.Create(aValue.ToString);
  564. end;
  565. end;
  566. tkSet :
  567. begin
  568. Result.JsonValue := TJSONString.Create(aValue.ToString);
  569. end;
  570. tkRecord :
  571. begin
  572. rRec := ctx.GetType(aValue.TypeInfo).AsRecord;
  573. try
  574. json := TJSONObject.Create;
  575. for rField in rRec.GetFields do
  576. begin
  577. json.AddPair(Serialize(rField.name,rField.GetValue(aValue.GetReferenceToRawData)));
  578. end;
  579. Result.JsonValue := json;
  580. finally
  581. ctx.Free;
  582. end;
  583. end;
  584. tkMethod, tkPointer, tkClassRef ,tkInterface, tkProcedure :
  585. begin
  586. //skip these properties
  587. FreeAndNil(Result);
  588. end
  589. else
  590. begin
  591. raise EJsonSerializeError.Create(Format(cNotSupportedDataType,[aName,GetTypeName(aValue.TypeInfo)]));
  592. end;
  593. end;
  594. except
  595. Result.Free;
  596. raise;
  597. end;
  598. end;
  599. { TCommentProperty }
  600. constructor TCommentProperty.Create(const aComment: string);
  601. begin
  602. fComment := aComment;
  603. end;
  604. { TCustomNameProperty }
  605. constructor TCustomNameProperty.Create(const aName: string);
  606. begin
  607. fName := aName;
  608. end;
  609. end.