/packages/fcl-db/src/json/fpjsondataset.pp

https://github.com/slibre/freepascal · Puppet · 1109 lines · 1104 code · 5 blank · 0 comment · 5 complexity · 3afc75b60775396eed70cd910b8210d9 MD5 · raw file

  1. {$mode objfpc}
  2. {$h+}
  3. unit fpjsondataset;
  4. interface
  5. uses
  6. DB, typinfo, Classes, SysUtils, fpjson;
  7. type
  8. { TJSONFieldMapper }
  9. // This class is responsible for mapping the field objects of the records.
  10. TJSONFieldMapper = Class(TObject)
  11. // Return row TJSONData instance with data for field 'FieldName' or 'FieldIndex'.
  12. Function GetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row : TJSONData) : TJSONData; virtual; abstract;
  13. // Same, but now based on TField.
  14. Function GetJSONDataForField(F : TField; Row : TJSONData) : TJSONData; virtual;
  15. // Set data for field 'FieldName' or 'FieldIndex' to supplied TJSONData instance in row
  16. procedure SetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row,Data : TJSONData); virtual; abstract;
  17. // Set data for field TField to supplied TJSONData instance
  18. procedure SetJSONDataForField(F : TField; Row,Data : TJSONData); virtual;
  19. // Create a new row.
  20. Function CreateRow : TJSONData; virtual; abstract;
  21. end;
  22. // JSON has no date/time type, so we use a string field.
  23. // ExtJS provides the date/time format in it's field config: 'dateFormat'
  24. // The below field classes store this in the NNNFormat field.
  25. { TJSONDateField }
  26. TJSONDateField = Class(TDateField)
  27. private
  28. FDateFormat: String;
  29. Published
  30. Property DateFormat : String Read FDateFormat Write FDateFormat;
  31. end;
  32. { TJSONTimeField }
  33. TJSONTimeField = Class(TTimeField)
  34. private
  35. FTimeFormat: String;
  36. Published
  37. Property TimeFormat : String Read FTimeFormat Write FTimeFormat;
  38. end;
  39. { TJSONDateTimeField }
  40. TJSONDateTimeField = Class(TDateTimeField)
  41. private
  42. FDateTimeFormat: String;
  43. Published
  44. Property DateTimeFormat : String Read FDateTimeFormat Write FDateTimeFormat;
  45. end;
  46. { TBaseJSONDataSet }
  47. // basic JSON dataset. Does nothing ExtJS specific.
  48. TBaseJSONDataSet = class (TDataSet)
  49. private
  50. FMUS: Boolean;
  51. FOwnsData : Boolean;
  52. FDefaultList : TFPList;
  53. FCurrentList: TFPList;
  54. FRecordSize: Integer;
  55. FCurrent: Integer;
  56. // Possible metadata to configure fields from.
  57. FMetaData : TJSONObject;
  58. // This will contain the rows.
  59. FRows : TJSONArray;
  60. FFieldMapper : TJSONFieldMapper;
  61. // When editing, this object is edited.
  62. FEditRow : TJSONData;
  63. procedure SetMetaData(AValue: TJSONObject);
  64. procedure SetRows(AValue: TJSONArray);
  65. protected
  66. // dataset virtual methods
  67. function AllocRecordBuffer: TRecordBuffer; override;
  68. procedure FreeRecordBuffer(var Buffer: TRecordBuffer); override;
  69. procedure InternalInitRecord(Buffer: TRecordBuffer); override;
  70. procedure GetBookmarkData(Buffer: TRecordBuffer; Data: Pointer); override;
  71. function GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag; override;
  72. function GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
  73. function GetRecordSize: Word; override;
  74. procedure InternalClose; override;
  75. procedure InternalDelete; override;
  76. procedure InternalFirst; override;
  77. procedure InternalGotoBookmark(ABookmark: Pointer); override;
  78. procedure InternalLast; override;
  79. procedure InternalOpen; override;
  80. procedure InternalPost; override;
  81. procedure InternalInsert; override;
  82. procedure InternalEdit; override;
  83. procedure InternalCancel; override;
  84. procedure InternalInitFieldDefs; override;
  85. procedure InternalSetToRecord(Buffer: TRecordBuffer); override;
  86. function GetFieldClass(FieldType: TFieldType): TFieldClass; override;
  87. function IsCursorOpen: Boolean; override;
  88. procedure SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag); override;
  89. procedure SetBookmarkData(Buffer: TRecordBuffer; Data: Pointer); override;
  90. function GetFieldData(Field: TField; Buffer: Pointer; NativeFormat : Boolean): Boolean; override;
  91. procedure SetFieldData(Field: TField; Buffer: Pointer; NativeFormat : Boolean); override;
  92. function GetRecordCount: Integer; override;
  93. procedure SetRecNo(Value: Integer); override;
  94. function GetRecNo: Integer; override;
  95. Protected
  96. // New methods.
  97. // Called when dataset is closed. If OwnsData is true, metadata and rows are freed.
  98. Procedure FreeData; virtual;
  99. // Fill default list.
  100. Procedure FillList; virtual;
  101. // Convert MetaData object to FieldDefs.
  102. Procedure MetaDataToFieldDefs; virtual; abstract;
  103. // Initialize Date/Time info in all date/time fields. Called during InternalOpen
  104. procedure InitDateTimeFields; virtual;
  105. // Convert JSON date S to DateTime for Field F
  106. function ConvertDateTimeField(S: String; F: TField): TDateTime; virtual;
  107. // Format JSON date to from DT for Field F
  108. function FormatDateTimeField(DT : TDateTime; F: TField): String; virtual;
  109. // Create fieldmapper. A descendent MUST implement this.
  110. Function CreateFieldMapper : TJSONFieldMapper; virtual; abstract;
  111. // If True, then the dataset will free MetaData and FRows when it is closed.
  112. Property OwnsData : Boolean Read FownsData Write FOwnsData;
  113. // set to true if unknown field types should be handled as string fields.
  114. Property MapUnknownToStringType : Boolean Read FMUS Write FMUS;
  115. // Metadata
  116. Property MetaData : TJSONObject Read FMetaData Write SetMetaData;
  117. // Rows
  118. Property Rows : TJSONArray Read FRows Write SetRows;
  119. public
  120. constructor Create (AOwner: TComponent); override;
  121. destructor Destroy; override;
  122. published
  123. Property FieldDefs;
  124. // redeclared data set properties
  125. property Active;
  126. property BeforeOpen;
  127. property AfterOpen;
  128. property BeforeClose;
  129. property AfterClose;
  130. property BeforeInsert;
  131. property AfterInsert;
  132. property BeforeEdit;
  133. property AfterEdit;
  134. property BeforePost;
  135. property AfterPost;
  136. property BeforeCancel;
  137. property AfterCancel;
  138. property BeforeDelete;
  139. property AfterDelete;
  140. property BeforeScroll;
  141. property AfterScroll;
  142. property OnCalcFields;
  143. property OnDeleteError;
  144. property OnEditError;
  145. property OnFilterRecord;
  146. property OnNewRecord;
  147. property OnPostError;
  148. end;
  149. { TExtJSJSONDataSet }
  150. // Base for ExtJS datasets. It handles MetaData conversion.
  151. TExtJSJSONDataSet = Class(TBaseJSONDataset)
  152. Private
  153. FFields : TJSONArray;
  154. Protected
  155. Function GenerateMetaData : TJSONObject;
  156. function ConvertDateFormat(S: String): String; virtual;
  157. Procedure MetaDataToFieldDefs; override;
  158. procedure InitDateTimeFields; override;
  159. function StringToFieldType(S: String): TFieldType;virtual;
  160. function GetStringFieldLength(F: TJSONObject; AName: String; AIndex: Integer): integer; virtual;
  161. Public
  162. // Use this to load MetaData/Rows from stream.
  163. // If no metadata is present in the stream, FieldDefs must be filled manually.
  164. Procedure LoadFromStream(S : TStream);
  165. // Use this to load MetaData/Rows from file.
  166. // If no metadata is present in the file, FieldDefs must be filled manually.
  167. Procedure LoadFromFile(Const AFileName: string);
  168. // Use this to save Rows and optionally metadata to Stream.
  169. // Note that MetaData must be set.
  170. Procedure SaveToStream(S : TStream; SaveMetaData : Boolean);
  171. // Use this to save Rows and optionally metadata to Stream.
  172. // Note that MetaData must be set.
  173. Procedure SaveToFile(Const AFileName : String; SaveMetaData : Boolean);
  174. // Can be set directly if the dataset is closed.
  175. Property MetaData;
  176. // Can be set directly if the dataset is closed. If metadata is set, it must match the data.
  177. Property Rows;
  178. Published
  179. Property OwnsData;
  180. end;
  181. { TExtJSJSONObjectDataSet }
  182. // Use this dataset for data where the data is an array of objects.
  183. TExtJSJSONObjectDataSet = Class(TExtJSJSONDataSet)
  184. Function CreateFieldMapper : TJSONFieldMapper; override;
  185. end;
  186. { TExtJSJSONArrayDataSet }
  187. // Use this dataset for data where the data is an array of arrays.
  188. TExtJSJSONArrayDataSet = Class(TExtJSJSONDataSet)
  189. Function CreateFieldMapper : TJSONFieldMapper; override;
  190. end;
  191. { TJSONObjectFieldMapper }
  192. // Fieldmapper to be used when the data is in an object
  193. TJSONObjectFieldMapper = Class(TJSONFieldMapper)
  194. procedure SetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row,Data : TJSONData); override;
  195. Function GetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row : TJSONData) : TJSONData; override;
  196. Function CreateRow : TJSONData; override;
  197. end;
  198. { TJSONArrayFieldMapper }
  199. // Fieldmapper to be used when the data is in an array
  200. TJSONArrayFieldMapper = Class(TJSONFieldMapper)
  201. procedure SetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row,Data : TJSONData); override;
  202. Function GetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row : TJSONData) : TJSONData; override;
  203. Function CreateRow : TJSONData; override;
  204. end;
  205. EJSONDataset = Class(EDatabaseError);
  206. implementation
  207. uses dateutils, jsonparser;
  208. type
  209. PRecInfo = ^TRecInfo;
  210. TRecInfo = record
  211. Index: Integer;
  212. Bookmark: Longint;
  213. BookmarkFlag: TBookmarkFlag;
  214. end;
  215. { TJSONFieldMapper }
  216. function TJSONFieldMapper.GetJSONDataForField(F: TField; Row: TJSONData
  217. ): TJSONData;
  218. begin
  219. // This supposes that Index is correct, i.e. the field positions have not been changed.
  220. Result:=GetJSONDataForField(F.FieldName,F.Index,Row);
  221. end;
  222. procedure TJSONFieldMapper.SetJSONDataForField(F: TField; Row,Data: TJSONData);
  223. begin
  224. SetJSONDataForField(F.FieldName,F.Index,Row,Data);
  225. end;
  226. { TJSONArrayDataSet }
  227. function TExtJSJSONArrayDataSet.CreateFieldMapper: TJSONFieldMapper;
  228. begin
  229. Result:=TJSONArrayFieldMapper.Create;
  230. end;
  231. { TJSONObjectDataSet }
  232. function TExtJSJSONObjectDataSet.CreateFieldMapper: TJSONFieldMapper;
  233. begin
  234. Result:=TJSONObjectFieldMapper.Create;
  235. end;
  236. { TJSONArrayFieldMapper }
  237. procedure TJSONArrayFieldMapper.SetJSONDataForField(const FieldName: String;
  238. FieldIndex: Integer; Row, Data: TJSONData);
  239. begin
  240. (Row as TJSONArray).Items[FieldIndex]:=Data;
  241. end;
  242. function TJSONArrayFieldMapper.GetJSONDataForField(Const FieldName: String;
  243. FieldIndex: Integer; Row: TJSONData): TJSONData;
  244. begin
  245. Result:=(Row as TJSONArray).Items[FieldIndex];
  246. end;
  247. function TJSONArrayFieldMapper.CreateRow: TJSONData;
  248. begin
  249. Result:=TJSONArray.Create;
  250. end;
  251. { TJSONObjectFieldMapper }
  252. procedure TJSONObjectFieldMapper.SetJSONDataForField(const FieldName: String;
  253. FieldIndex: Integer; Row, Data: TJSONData);
  254. begin
  255. (Row as TJSONObject).Elements[FieldName]:=Data;
  256. end;
  257. function TJSONObjectFieldMapper.GetJSONDataForField(const FieldName: String;
  258. FieldIndex: Integer; Row: TJSONData): TJSONData;
  259. begin
  260. Result:=(Row as TJSONObject).Elements[FieldName];
  261. end;
  262. function TJSONObjectFieldMapper.CreateRow: TJSONData;
  263. begin
  264. Result:=TJSONObject.Create;
  265. end;
  266. procedure TBaseJSONDataSet.SetMetaData(AValue: TJSONObject);
  267. begin
  268. CheckInActive;
  269. if FMetaData=AValue then
  270. Exit;
  271. If OwnsData then
  272. FreeAndNil(FMetaData);
  273. FMetaData:=AValue;
  274. end;
  275. procedure TBaseJSONDataSet.SetRows(AValue: TJSONArray);
  276. begin
  277. CheckInActive;
  278. if FRows=AValue then Exit;
  279. If OwnsData then
  280. FreeAndNil(FRows);
  281. FRows:=AValue;
  282. end;
  283. function TBaseJSONDataSet.AllocRecordBuffer: TRecordBuffer;
  284. begin
  285. Result := TRecordBuffer(StrAlloc(fRecordSize));
  286. end;
  287. // the next two are particularly ugly.
  288. procedure TBaseJSONDataSet.InternalInitRecord(Buffer: TRecordBuffer);
  289. begin
  290. FillChar(Buffer^, FRecordSize, 0);
  291. end;
  292. procedure TBaseJSONDataSet.FreeRecordBuffer (var Buffer: TRecordBuffer);
  293. begin
  294. StrDispose(pansichar(Buffer));
  295. end;
  296. procedure TBaseJSONDataSet.GetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
  297. begin
  298. PInteger(Data)^ := PRecInfo(Buffer)^.Bookmark;
  299. end;
  300. function TBaseJSONDataSet.GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag;
  301. begin
  302. Result := PRecInfo(Buffer)^.BookmarkFlag;
  303. end;
  304. function TBaseJSONDataSet.GetRecNo: Integer;
  305. begin
  306. Result := FCurrent + 1;
  307. end;
  308. procedure TBaseJSONDataSet.InternalInitFieldDefs;
  309. begin
  310. If Assigned(FMetaData) then
  311. MetaDataToFieldDefs;
  312. if (FieldDefs.Count=0) then
  313. Raise EJSONDataset.Create('No fields found');
  314. end;
  315. procedure TBaseJSONDataSet.FreeData;
  316. begin
  317. If FOwnsData then
  318. begin
  319. FreeAndNil(FRows);
  320. FreeAndNil(FMetaData);
  321. end;
  322. if (FCurrentList<>FDefaultList) then
  323. FreeAndNil(FCurrentList)
  324. else
  325. FCurrentList:=Nil;
  326. FreeAndNil(FDefaultList);
  327. FreeAndNil(FFieldMapper);
  328. FCurrentList:=Nil;
  329. end;
  330. procedure TBaseJSONDataSet.FillList;
  331. Var
  332. I : Integer;
  333. begin
  334. FDefaultList:=TFPList.Create;
  335. For I:=0 to FRows.Count-1 do
  336. FDefaultList.Add(FRows[i]);
  337. FCurrentList:=FDefaultList;
  338. end;
  339. Function TExtJSJSONDataSet.StringToFieldType(S : String) : TFieldType;
  340. begin
  341. if (s='int') then
  342. Result:=ftLargeInt
  343. else if (s='float') then
  344. Result:=ftFloat
  345. else if (s='boolean') then
  346. Result:=ftBoolean
  347. else if (s='date') then
  348. Result:=ftDateTime
  349. else if (s='string') or (s='auto') or (s='') then
  350. Result:=ftString
  351. else
  352. if MapUnknownToStringType then
  353. Result:=ftString
  354. else
  355. Raise EJSONDataset.CreateFmt('Unknown JSON data type : %s',[s]);
  356. end;
  357. Function TExtJSJSONDataSet.GetStringFieldLength(F : TJSONObject; AName : String; AIndex : Integer) : integer;
  358. Var
  359. I,L : Integer;
  360. D : TJSONData;
  361. begin
  362. Result:=0;
  363. I:=F.IndexOfName('maxlen');
  364. if (I<>-1) and (F.Items[I].jsonType=jtNumber) then
  365. begin
  366. Result:=StrToIntDef(trim(F.Items[i].AsString),-1);
  367. if (Result=-1) then
  368. Raise EJSONDataset.CreateFmt('Invalid maximum length specifier for field %s : %s',[AName,F.Items[i].AsString])
  369. end
  370. else
  371. begin
  372. For I:=0 to FRows.Count-1 do
  373. begin
  374. D:=FFieldMapper.GetJSONDataForField(Aname,AIndex,FRows[i]);
  375. if (D<>Nil) and (D.JsonType<>jtNull) then
  376. begin
  377. l:=Length(D.AsString);
  378. if L>Result then
  379. Result:=L;
  380. end;
  381. end;
  382. end;
  383. if (Result=0) then
  384. Result:=20;
  385. end;
  386. procedure TExtJSJSONDataSet.LoadFromStream(S: TStream);
  387. Var
  388. P : TJSONParser;
  389. D : TJSONData;
  390. O : TJSONObject;
  391. N : String;
  392. I : Integer;
  393. begin
  394. P:=TJSONParser.Create(S);
  395. try
  396. D:=P.Parse;
  397. try
  398. if (D.JSONType=jtObject) then
  399. O:=D as TJSONObject
  400. else
  401. begin
  402. FreeAndNil(D);
  403. Raise EJSONDataset.Create('Not a valid ExtJS JSON data packet');
  404. end;
  405. N:='rows';
  406. // Check metadata
  407. I:=O.IndexOfName('metaData');
  408. if (I<>-1) then
  409. begin
  410. If (O.Items[i].JSONType<>jtObject) then
  411. Raise EJSONDataset.Create('Invalid ExtJS JSON metaData in data packet.');
  412. Metadata:=O.Objects['metaData'];
  413. O.Extract(I);
  414. I:=Metadata.IndexOfName('root');
  415. If (I<>-1) then
  416. begin
  417. if (MetaData.Items[i].JSONType<>jtString) then
  418. Raise EJSONDataset.Create('Invalid ExtJS JSON root element in metaData.');
  419. N:=MetaData.Strings['root'];
  420. end;
  421. end;
  422. // Check rows
  423. I:=O.IndexOfName(N);
  424. if (I=-1) then
  425. Raise EJSONDataset.Create('Missing rows in data packet');
  426. if (O.Items[i].JSONType<>jtArray) then
  427. Raise EJSONDataset.Create('Rows element must be an array');
  428. Rows:=O.Items[i] as TJSONArray;
  429. O.Extract(I);
  430. OwnsData:=True;
  431. finally
  432. D.Free;
  433. end;
  434. finally
  435. P.Free;
  436. end;
  437. end;
  438. procedure TExtJSJSONDataSet.LoadFromFile(const AFileName: string);
  439. Var
  440. F : TFileStream;
  441. begin
  442. F:=TFileStream.Create(AFileName,fmOpenRead or fmShareDenyWrite);
  443. try
  444. LoadFromStream(F);
  445. finally
  446. F.Free;
  447. end;
  448. end;
  449. procedure TExtJSJSONDataSet.SaveToStream(S: TStream; SaveMetaData: Boolean);
  450. Var
  451. O : TJSONObject;
  452. SS : TStringStream;
  453. N : String;
  454. I : Integer;
  455. M : TJSONobject;
  456. begin
  457. O:=TJSONObject.Create;
  458. try
  459. N:='rows';
  460. If SaveMetaData then
  461. begin
  462. M:=MetaData;
  463. if M=Nil then
  464. M:=GenerateMetaData;
  465. O.Add('metaData',M);
  466. if M.IndexOfName('root')<>-1 then
  467. N:=M.Strings['root'];
  468. end;
  469. O.Add(N,Rows);
  470. SS:=TStringStream.Create(O.FormatJSON());
  471. try
  472. S.CopyFrom(SS,0);
  473. finally
  474. SS.Free;
  475. end;
  476. finally
  477. If (MetaData<>Nil) and SaveMetaData then
  478. begin
  479. I:=O.IndexOfName('metaData');
  480. if (I<>-1) then
  481. O.Extract(i);
  482. end;
  483. O.Extract(O.IndexOfName(N));
  484. O.Free;
  485. end;
  486. end;
  487. procedure TExtJSJSONDataSet.SaveToFile(const AFileName: String;
  488. SaveMetaData: Boolean);
  489. Var
  490. F : TFileStream;
  491. begin
  492. F:=TFileStream.Create(AFileName,fmCreate);
  493. try
  494. SaveToStream(F,SaveMetaData);
  495. finally
  496. F.Free;
  497. end;
  498. end;
  499. procedure TExtJSJSONDataSet.MetaDataToFieldDefs;
  500. Var
  501. A : TJSONArray;
  502. F : TJSONObject;
  503. I,J,FS : Integer;
  504. N,idf : String;
  505. ft: TFieldType;
  506. D : TJSONData;
  507. begin
  508. FieldDefs.Clear;
  509. I:=FMetadata.IndexOfName('fields');
  510. if (I=-1) or (FMetaData.Items[i].JSONType<>jtArray) then
  511. Raise EJSONDataset.Create('Invalid metadata object');
  512. A:=FMetadata.Arrays['fields'];
  513. For I:=0 to A.Count-1 do
  514. begin
  515. If (A.Types[i]<>jtObject) then
  516. Raise EJSONDataset.CreateFmt('Field definition %d in metadata (%s) is not an object',[i,A[i].AsJSON]);
  517. F:=A.Objects[i];
  518. J:=F.IndexOfName('name');
  519. If (J=-1) or (F.Items[J].JSONType<>jtString) then
  520. Raise EJSONDataset.CreateFmt('Field definition %d in has no or invalid name property',[i]);
  521. N:=F.Items[J].AsString;
  522. J:=F.IndexOfName('type');
  523. If (J=-1) then
  524. ft:=ftstring
  525. else If (F.Items[J].JSONType<>jtString) then
  526. Raise EJSONDataset.CreateFmt('Field definition %d in has invalid type property',[i])
  527. else
  528. ft:=StringToFieldType(F.Items[J].asString);
  529. if (ft=ftString) then
  530. fs:=GetStringFieldLength(F,N,I)
  531. else
  532. fs:=0;
  533. FieldDefs.Add(N,ft,fs);
  534. end;
  535. FFields:=A;
  536. end;
  537. function TExtJSJSONDataSet.GenerateMetaData: TJSONObject;
  538. Var
  539. F : TJSONArray;
  540. O : TJSONObject;
  541. I,M : Integer;
  542. T : STring;
  543. begin
  544. Result:=TJSONObject.Create;
  545. F:=TJSONArray.Create;
  546. Result.Add('fields',F);
  547. For I:=0 to FieldDefs.Count -1 do
  548. begin
  549. O:=TJSONObject.Create(['name',FieldDefs[i].name]);
  550. F.Add(O);
  551. M:=0;
  552. case FieldDefs[i].DataType of
  553. ftfixedwidechar,
  554. ftwideString,
  555. ftfixedchar,
  556. ftString:
  557. begin
  558. T:='string';
  559. M:=FieldDefs[i].Size;
  560. end;
  561. ftBoolean: T:='boolean';
  562. ftDate,
  563. ftTime,
  564. ftDateTime: T:='date';
  565. ftFloat: t:='float';
  566. ftSmallint,
  567. ftInteger,
  568. ftAutoInc,
  569. ftLargeInt,
  570. ftword: t:='int';
  571. else
  572. Raise EJSONDataset.CreateFmt('Unsupported field type : %s',[GetEnumName(TypeInfo(TFieldType),Ord(FieldDefs[i].DataType))]);
  573. end; // case
  574. O.Strings['type']:=t;
  575. if M<>0 then
  576. O.Integers['maxlen']:=M;
  577. end;
  578. Result.strings['root']:='rows';
  579. end;
  580. Function TExtJSJSONDataSet.ConvertDateFormat(S : String) : String;
  581. { Not handled: N S w z W t L o O P T Z c U MS }
  582. begin
  583. Result:=StringReplace(S,'y','yy',[rfReplaceall]);
  584. Result:=StringReplace(Result,'Y','yyyy',[rfReplaceall]);
  585. Result:=StringReplace(Result,'g','h',[rfReplaceall]);
  586. Result:=StringReplace(Result,'G','hh',[rfReplaceall]);
  587. Result:=StringReplace(Result,'F','mmmm',[rfReplaceall]);
  588. Result:=StringReplace(Result,'M','mmm',[rfReplaceall]);
  589. Result:=StringReplace(Result,'n','m',[rfReplaceall]);
  590. Result:=StringReplace(Result,'D','ddd',[rfReplaceall]);
  591. Result:=StringReplace(Result,'j','d',[rfReplaceall]);
  592. Result:=StringReplace(Result,'l','dddd',[rfReplaceall]);
  593. Result:=StringReplace(Result,'i','nn',[rfReplaceall]);
  594. Result:=StringReplace(Result,'u','zzz',[rfReplaceall]);
  595. Result:=StringReplace(Result,'a','am/pm',[rfReplaceall,rfIgnoreCase]);
  596. Result:=LowerCase(Result);
  597. end;
  598. procedure TExtJSJSONDataSet.InitDateTimeFields;
  599. Var
  600. F : TJSONObject;
  601. FF : TField;
  602. I,J : Integer;
  603. Fmt : String;
  604. begin
  605. If (FFields=Nil) then
  606. Exit;
  607. For I:=0 to FFields.Count-1 do
  608. begin
  609. F:=FFields.Objects[i];
  610. J:=F.IndexOfName('type');
  611. if (J<>-1) and (F.Items[J].JSONType=jtString) and (F.items[J].AsString='date') then
  612. begin
  613. J:=F.IndexOfName('dateFormat');
  614. if (J<>-1) and (F.Items[J].JSONType=jtString) then
  615. begin
  616. FMT:=ConvertDateFormat(F.Items[J].AsString);
  617. FF:=FindField(F.Strings['name']);
  618. if (FF<>Nil) and (FF.DataType in [ftDate,ftTime,ftDateTime]) and (FF.FieldKind=fkData) then
  619. begin
  620. if FF is TJSONDateField then
  621. TJSONDateField(FF).DateFormat:=Fmt
  622. else if FF is TJSONTimeField then
  623. TJSONTimeField(FF).TimeFormat:=Fmt
  624. else if FF is TJSONDateTimeField then
  625. TJSONDateTimeField(FF).DateTimeFormat:=Fmt;
  626. end;
  627. end;
  628. end;
  629. end;
  630. end;
  631. function TBaseJSONDataSet.GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode;
  632. DoCheck: Boolean): TGetResult;
  633. begin
  634. Result := grOK; // default
  635. case GetMode of
  636. gmNext: // move on
  637. if fCurrent < fCurrentList.Count - 1 then
  638. Inc (fCurrent)
  639. else
  640. Result := grEOF; // end of file
  641. gmPrior: // move back
  642. if fCurrent > 0 then
  643. Dec (fCurrent)
  644. else
  645. Result := grBOF; // begin of file
  646. gmCurrent: // check if empty
  647. if fCurrent >= fCurrentList.Count then
  648. Result := grEOF;
  649. end;
  650. if Result = grOK then // read the data
  651. with PRecInfo(Buffer)^ do
  652. begin
  653. Index := fCurrent;
  654. BookmarkFlag := bfCurrent;
  655. Bookmark := fCurrent;
  656. end;
  657. end;
  658. function TBaseJSONDataSet.GetRecordCount: Integer;
  659. begin
  660. Result := FCurrentList.Count;
  661. end;
  662. function TBaseJSONDataSet.GetRecordSize: Word;
  663. begin
  664. Result := SizeOf(Integer); // actual data without house-keeping
  665. end;
  666. procedure TBaseJSONDataSet.InternalClose;
  667. begin
  668. // disconnet and destroy field objects
  669. BindFields (False);
  670. if DefaultFields then
  671. DestroyFields;
  672. FreeData;
  673. end;
  674. procedure TBaseJSONDataSet.InternalDelete;
  675. Var
  676. R : TJSONData;
  677. begin
  678. R:=TJSONData(FCurrentList[FCurrent]);
  679. FCurrentList.Delete(FCurrent);
  680. if (FCurrent>=FCurrentList.Count) then
  681. Dec(FCurrent);
  682. FRows.Remove(R);
  683. end;
  684. procedure TBaseJSONDataSet.InternalFirst;
  685. begin
  686. FCurrent := -1;
  687. end;
  688. procedure TBaseJSONDataSet.InternalGotoBookmark(ABookmark: Pointer);
  689. begin
  690. if (ABookmark <> nil) then
  691. FCurrent := Integer (ABookmark);
  692. end;
  693. procedure TBaseJSONDataSet.InternalInsert;
  694. Var
  695. I : Integer;
  696. D : TFieldDef;
  697. begin
  698. FEditRow:=FFieldMapper.CreateRow;
  699. For I:=0 to FieldDefs.Count-1 do
  700. begin
  701. D:=FieldDefs[i];
  702. FFieldMapper.SetJSONDataForField(D.Name,D.Index,FEditRow,TJSONNull.Create);
  703. end;
  704. end;
  705. procedure TBaseJSONDataSet.InternalEdit;
  706. begin
  707. FEditRow:=TJSONData(FCurrentList[FCurrent]).Clone;
  708. end;
  709. procedure TBaseJSONDataSet.InternalCancel;
  710. begin
  711. FreeAndNil(FEditRow);
  712. end;
  713. procedure TBaseJSONDataSet.InternalLast;
  714. begin
  715. FCurrent:=FCurrentList.Count-1;
  716. end;
  717. procedure TBaseJSONDataSet.InitDateTimeFields;
  718. begin
  719. // Do nothing
  720. end;
  721. procedure TBaseJSONDataSet.InternalOpen;
  722. begin
  723. FreeAndNil(FFieldMapper);
  724. FFieldMapper:=CreateFieldMapper;
  725. IF (FRows=Nil) then // opening from fielddefs ?
  726. begin
  727. FRows:=TJSONArray.Create;
  728. OwnsData:=True;
  729. end;
  730. FillList;
  731. InternalInitFieldDefs;
  732. if DefaultFields then
  733. CreateFields;
  734. BindFields (True);
  735. InitDateTimeFields;
  736. FRecordSize := sizeof (TRecInfo);
  737. FCurrent := -1;
  738. BookmarkSize := sizeOf (Integer);
  739. end;
  740. procedure TBaseJSONDataSet.InternalPost;
  741. Var
  742. RI,I : integer;
  743. begin
  744. GetBookMarkData(ActiveBuffer,@I);
  745. if (State=dsInsert) then
  746. begin // Insert or Append
  747. FRows.Add(FEditRow);
  748. if GetBookMarkFlag(ActiveBuffer)=bfEOF then
  749. begin // Append
  750. FDefaultList.Add(FEditRow);
  751. if (FCurrentList<>FDefaultList) then
  752. FCurrentList.Add(FEditRow);
  753. end
  754. else // insert
  755. begin
  756. FCurrentList.Insert(FCurrent,FEditRow);
  757. if (FCurrentList<>FDefaultList) then
  758. FDefaultList.Add(FEditRow);
  759. end;
  760. end
  761. else
  762. begin // Edit
  763. RI:=FRows.IndexOf(TJSONData(FCurrentList[FCurrent]));
  764. if (RI<>-1) then
  765. FRows[RI]:=FEditRow
  766. else
  767. FRows.Add(FEditRow);
  768. FCurrentList[FCurrent]:=FEditRow;
  769. if (FCurrentList<>FDefaultList) then
  770. FDefaultList[FCurrent]:=FEditRow;
  771. end;
  772. FEditRow:=Nil;
  773. end;
  774. procedure TBaseJSONDataSet.InternalSetToRecord(Buffer: TRecordBuffer);
  775. begin
  776. FCurrent := PRecInfo(Buffer)^.Index;
  777. end;
  778. function TBaseJSONDataSet.GetFieldClass(FieldType: TFieldType): TFieldClass;
  779. begin
  780. case FieldType of
  781. ftDate : Result:=TJSONDateField;
  782. ftDateTime : Result:=TJSONDateTimeField;
  783. ftTime : Result:=TJSONTimeField;
  784. else
  785. Result:=inherited GetFieldClass(FieldType);
  786. end;
  787. end;
  788. function TBaseJSONDataSet.IsCursorOpen: Boolean;
  789. begin
  790. Result := Assigned(FDefaultList);
  791. end;
  792. procedure TBaseJSONDataSet.SetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
  793. begin
  794. PRecInfo(Buffer)^.Bookmark := PInteger(Data)^;
  795. end;
  796. function TBaseJSONDataSet.ConvertDateTimeField(S : String; F : TField) : TDateTime;
  797. Var
  798. Ptrn : string;
  799. begin
  800. Result:=0;
  801. Case F.DataType of
  802. ftDate : Ptrn:=TJSONDateField(F).DateFormat;
  803. ftTime : Ptrn:=TJSONTimeField(F).TimeFormat;
  804. ftDateTime : Ptrn:=TJSONDateTimeField(F).DateTimeFormat;
  805. end;
  806. If (Ptrn='') then
  807. Case F.DataType of
  808. ftDate : Result:=StrToDate(S);
  809. ftTime : Result:=StrToTime(S);
  810. ftDateTime : Result:=StrToDateTime(S);
  811. end
  812. else
  813. begin
  814. Result:=ScanDateTime(ptrn,S,1);
  815. end;
  816. end;
  817. function TBaseJSONDataSet.FormatDateTimeField(DT: TDateTime; F: TField
  818. ): String;
  819. Var
  820. Ptrn : string;
  821. begin
  822. Result:='';
  823. Case F.DataType of
  824. ftDate : Ptrn:=TJSONDateField(F).DateFormat;
  825. ftTime : Ptrn:=TJSONTimeField(F).TimeFormat;
  826. ftDateTime : Ptrn:=TJSONDateTimeField(F).DateTimeFormat;
  827. end;
  828. If (Ptrn='') then
  829. Case F.DataType of
  830. ftDate : Result:=DateToStr(DT);
  831. ftTime : Result:=TimeToStr(DT);
  832. ftDateTime : Result:=DateTimeToStr(DT);
  833. end
  834. else
  835. Result:=FormatDateTime(ptrn,DT);
  836. end;
  837. function TBaseJSONDataSet.GetFieldData(Field: TField; Buffer: Pointer
  838. ; NativeFormat : Boolean): Boolean;
  839. var
  840. R,F : TJSONData;
  841. B : WordBool;
  842. s: string;
  843. w : widestring;
  844. D : TDateTime;
  845. FV : Double;
  846. I : Longint;
  847. li : int64;
  848. begin
  849. I:=PRecInfo(ActiveBuffer)^.Index;
  850. // Writeln('Index : ',I,'<',FCurrentList.Count,' ?');
  851. if (I<>-1) then
  852. R:=TJSONData(FCurrentList[i])
  853. else
  854. R:=FEditRow;
  855. F:=FFieldMapper.GetJSONDataForField(Field,R);
  856. Result:=(F<>Nil) and not (F.JSONType in [jtUnknown,jtNull]);
  857. if not Result then
  858. exit;
  859. case Field.DataType of
  860. ftfixedwidechar,
  861. ftwideString:
  862. begin
  863. W:=F.AsString;
  864. if (length(W)>0) then
  865. Move(W[1],Buffer^,Length(W)*SizeOf(Widechar)+1)
  866. else
  867. PChar(Buffer)^:=#0;
  868. end;
  869. ftfixedchar,
  870. ftString:
  871. begin
  872. S:=F.AsString;
  873. if (length(s)>0) then
  874. Move(S[1],Buffer^,Length(S)+1)
  875. else
  876. PChar(Buffer)^:=#0;
  877. end;
  878. ftBoolean:
  879. begin
  880. B:=F.AsBoolean;
  881. Move(B,Buffer^,sizeof(WordBool));
  882. end;
  883. ftDate,
  884. ftTime,
  885. ftDateTime:
  886. begin
  887. D:=ConvertDateTimeField(F.AsString,Field);
  888. Move(D,Buffer^,sizeof(TDateTime));
  889. end;
  890. ftFloat:
  891. begin
  892. Fv:=F.asFloat;
  893. Move(FV,Buffer^,sizeof(Double));
  894. end;
  895. ftSmallint,
  896. ftInteger,
  897. ftAutoInc,
  898. ftword:
  899. begin
  900. I:=F.AsInteger;
  901. Move(I,Buffer^,SizeOf(I));
  902. end;
  903. ftLargeint:
  904. begin
  905. LI:=F.AsInt64;
  906. Move(LI,Buffer^,SizeOf(LI));
  907. end;
  908. else
  909. Raise EJSONDataset.CreateFmt('Unsupported field type : %s',[GetEnumName(TypeInfo(TFieldType),Ord(Field.DataType))]);
  910. end; // case
  911. end;
  912. procedure TBaseJSONDataSet.SetFieldData(Field: TField; Buffer: Pointer; NativeFormat : Boolean);
  913. var
  914. R,F : TJSONData;
  915. B : PWordBool;
  916. s: string;
  917. w : widestring;
  918. D : TDateTime;
  919. FV : Double;
  920. I : Longint;
  921. li : int64;
  922. begin
  923. F:=Nil;
  924. if (Buffer<>nil) then
  925. case Field.DataType of
  926. ftfixedwidechar,
  927. ftwideString:
  928. begin
  929. SetLength(W,Field.Size);
  930. if (length(W)>0) then
  931. Move(Buffer^,W[1],Field.Size*SizeOf(Widechar));
  932. F:=TJSONString.Create(W);
  933. end;
  934. ftfixedchar,
  935. ftString:
  936. F:=TJSONString.Create(StrPas(Buffer));
  937. ftBoolean:
  938. F:=TJSONBoolean.Create(PWordBool(Buffer)^);
  939. ftDate,
  940. ftTime,
  941. ftDateTime:
  942. begin
  943. S:=FormatDateTimeField(PDateTime(Buffer)^,Field);
  944. F:=TJSONString.Create(S);
  945. end;
  946. ftFloat:
  947. F:=TJSONFloatNumber.Create(PDouble(Buffer)^);
  948. ftSmallint,
  949. ftInteger,
  950. ftAutoInc,
  951. ftword:
  952. F:=TJSONIntegerNumber.Create(PLongint(Buffer)^);
  953. ftLargeint:
  954. begin
  955. F:=TJSONInt64Number.Create(PInt64(Buffer)^);
  956. end;
  957. else
  958. Raise EJSONDataset.CreateFmt('Unsupported field type : %s',[GetEnumName(TypeInfo(TFieldType),Ord(Field.DataType))]);
  959. end; // case
  960. if (F=Nil) then
  961. F:=TJSONNull.Create;
  962. // Writeln('Set field data : ',F.AsJSON);
  963. FFieldMapper.SetJSONDataForField(Field,FEditRow,F);
  964. // Writeln('Field data is set : ',FEditRow.AsJSON);
  965. end;
  966. procedure TBaseJSONDataSet.SetBookmarkFlag(Buffer: TRecordBuffer;
  967. Value: TBookmarkFlag);
  968. begin
  969. PRecInfo(Buffer)^.BookmarkFlag := Value;
  970. end;
  971. procedure TBaseJSONDataSet.SetRecNo(Value: Integer);
  972. begin
  973. if (Value < 0) or (Value > FCurrentList.Count) then
  974. raise EJSONDataset.CreateFmt('SetRecNo: index %d out of range',[Value]);
  975. FCurrent := Value - 1;
  976. Resync([]);
  977. DoAfterScroll;
  978. end;
  979. constructor TBaseJSONDataSet.Create(AOwner: TComponent);
  980. begin
  981. inherited;
  982. FownsData:=True;
  983. end;
  984. destructor TBaseJSONDataSet.Destroy;
  985. begin
  986. FreeData;
  987. inherited;
  988. end;
  989. end.