/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
- {$mode objfpc}
- {$h+}
- unit fpjsondataset;
- interface
- uses
- DB, typinfo, Classes, SysUtils, fpjson;
- type
- { TJSONFieldMapper }
- // This class is responsible for mapping the field objects of the records.
- TJSONFieldMapper = Class(TObject)
- // Return row TJSONData instance with data for field 'FieldName' or 'FieldIndex'.
- Function GetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row : TJSONData) : TJSONData; virtual; abstract;
- // Same, but now based on TField.
- Function GetJSONDataForField(F : TField; Row : TJSONData) : TJSONData; virtual;
- // Set data for field 'FieldName' or 'FieldIndex' to supplied TJSONData instance in row
- procedure SetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row,Data : TJSONData); virtual; abstract;
- // Set data for field TField to supplied TJSONData instance
- procedure SetJSONDataForField(F : TField; Row,Data : TJSONData); virtual;
- // Create a new row.
- Function CreateRow : TJSONData; virtual; abstract;
- end;
- // JSON has no date/time type, so we use a string field.
- // ExtJS provides the date/time format in it's field config: 'dateFormat'
- // The below field classes store this in the NNNFormat field.
- { TJSONDateField }
- TJSONDateField = Class(TDateField)
- private
- FDateFormat: String;
- Published
- Property DateFormat : String Read FDateFormat Write FDateFormat;
- end;
- { TJSONTimeField }
- TJSONTimeField = Class(TTimeField)
- private
- FTimeFormat: String;
- Published
- Property TimeFormat : String Read FTimeFormat Write FTimeFormat;
- end;
- { TJSONDateTimeField }
- TJSONDateTimeField = Class(TDateTimeField)
- private
- FDateTimeFormat: String;
- Published
- Property DateTimeFormat : String Read FDateTimeFormat Write FDateTimeFormat;
- end;
- { TBaseJSONDataSet }
- // basic JSON dataset. Does nothing ExtJS specific.
- TBaseJSONDataSet = class (TDataSet)
- private
- FMUS: Boolean;
- FOwnsData : Boolean;
- FDefaultList : TFPList;
- FCurrentList: TFPList;
- FRecordSize: Integer;
- FCurrent: Integer;
- // Possible metadata to configure fields from.
- FMetaData : TJSONObject;
- // This will contain the rows.
- FRows : TJSONArray;
- FFieldMapper : TJSONFieldMapper;
- // When editing, this object is edited.
- FEditRow : TJSONData;
- procedure SetMetaData(AValue: TJSONObject);
- procedure SetRows(AValue: TJSONArray);
- protected
- // dataset virtual methods
- function AllocRecordBuffer: TRecordBuffer; override;
- procedure FreeRecordBuffer(var Buffer: TRecordBuffer); override;
- procedure InternalInitRecord(Buffer: TRecordBuffer); override;
- procedure GetBookmarkData(Buffer: TRecordBuffer; Data: Pointer); override;
- function GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag; override;
- function GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
- function GetRecordSize: Word; override;
- procedure InternalClose; override;
- procedure InternalDelete; override;
- procedure InternalFirst; override;
- procedure InternalGotoBookmark(ABookmark: Pointer); override;
- procedure InternalLast; override;
- procedure InternalOpen; override;
- procedure InternalPost; override;
- procedure InternalInsert; override;
- procedure InternalEdit; override;
- procedure InternalCancel; override;
- procedure InternalInitFieldDefs; override;
- procedure InternalSetToRecord(Buffer: TRecordBuffer); override;
- function GetFieldClass(FieldType: TFieldType): TFieldClass; override;
- function IsCursorOpen: Boolean; override;
- procedure SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag); override;
- procedure SetBookmarkData(Buffer: TRecordBuffer; Data: Pointer); override;
- function GetFieldData(Field: TField; Buffer: Pointer; NativeFormat : Boolean): Boolean; override;
- procedure SetFieldData(Field: TField; Buffer: Pointer; NativeFormat : Boolean); override;
- function GetRecordCount: Integer; override;
- procedure SetRecNo(Value: Integer); override;
- function GetRecNo: Integer; override;
- Protected
- // New methods.
- // Called when dataset is closed. If OwnsData is true, metadata and rows are freed.
- Procedure FreeData; virtual;
- // Fill default list.
- Procedure FillList; virtual;
- // Convert MetaData object to FieldDefs.
- Procedure MetaDataToFieldDefs; virtual; abstract;
- // Initialize Date/Time info in all date/time fields. Called during InternalOpen
- procedure InitDateTimeFields; virtual;
- // Convert JSON date S to DateTime for Field F
- function ConvertDateTimeField(S: String; F: TField): TDateTime; virtual;
- // Format JSON date to from DT for Field F
- function FormatDateTimeField(DT : TDateTime; F: TField): String; virtual;
- // Create fieldmapper. A descendent MUST implement this.
- Function CreateFieldMapper : TJSONFieldMapper; virtual; abstract;
- // If True, then the dataset will free MetaData and FRows when it is closed.
- Property OwnsData : Boolean Read FownsData Write FOwnsData;
- // set to true if unknown field types should be handled as string fields.
- Property MapUnknownToStringType : Boolean Read FMUS Write FMUS;
- // Metadata
- Property MetaData : TJSONObject Read FMetaData Write SetMetaData;
- // Rows
- Property Rows : TJSONArray Read FRows Write SetRows;
- public
- constructor Create (AOwner: TComponent); override;
- destructor Destroy; override;
- published
- Property FieldDefs;
- // redeclared data set properties
- property Active;
- property BeforeOpen;
- property AfterOpen;
- property BeforeClose;
- property AfterClose;
- property BeforeInsert;
- property AfterInsert;
- property BeforeEdit;
- property AfterEdit;
- property BeforePost;
- property AfterPost;
- property BeforeCancel;
- property AfterCancel;
- property BeforeDelete;
- property AfterDelete;
- property BeforeScroll;
- property AfterScroll;
- property OnCalcFields;
- property OnDeleteError;
- property OnEditError;
- property OnFilterRecord;
- property OnNewRecord;
- property OnPostError;
- end;
- { TExtJSJSONDataSet }
- // Base for ExtJS datasets. It handles MetaData conversion.
- TExtJSJSONDataSet = Class(TBaseJSONDataset)
- Private
- FFields : TJSONArray;
- Protected
- Function GenerateMetaData : TJSONObject;
- function ConvertDateFormat(S: String): String; virtual;
- Procedure MetaDataToFieldDefs; override;
- procedure InitDateTimeFields; override;
- function StringToFieldType(S: String): TFieldType;virtual;
- function GetStringFieldLength(F: TJSONObject; AName: String; AIndex: Integer): integer; virtual;
- Public
- // Use this to load MetaData/Rows from stream.
- // If no metadata is present in the stream, FieldDefs must be filled manually.
- Procedure LoadFromStream(S : TStream);
- // Use this to load MetaData/Rows from file.
- // If no metadata is present in the file, FieldDefs must be filled manually.
- Procedure LoadFromFile(Const AFileName: string);
- // Use this to save Rows and optionally metadata to Stream.
- // Note that MetaData must be set.
- Procedure SaveToStream(S : TStream; SaveMetaData : Boolean);
- // Use this to save Rows and optionally metadata to Stream.
- // Note that MetaData must be set.
- Procedure SaveToFile(Const AFileName : String; SaveMetaData : Boolean);
- // Can be set directly if the dataset is closed.
- Property MetaData;
- // Can be set directly if the dataset is closed. If metadata is set, it must match the data.
- Property Rows;
- Published
- Property OwnsData;
- end;
- { TExtJSJSONObjectDataSet }
- // Use this dataset for data where the data is an array of objects.
- TExtJSJSONObjectDataSet = Class(TExtJSJSONDataSet)
- Function CreateFieldMapper : TJSONFieldMapper; override;
- end;
- { TExtJSJSONArrayDataSet }
- // Use this dataset for data where the data is an array of arrays.
- TExtJSJSONArrayDataSet = Class(TExtJSJSONDataSet)
- Function CreateFieldMapper : TJSONFieldMapper; override;
- end;
- { TJSONObjectFieldMapper }
- // Fieldmapper to be used when the data is in an object
- TJSONObjectFieldMapper = Class(TJSONFieldMapper)
- procedure SetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row,Data : TJSONData); override;
- Function GetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row : TJSONData) : TJSONData; override;
- Function CreateRow : TJSONData; override;
- end;
- { TJSONArrayFieldMapper }
- // Fieldmapper to be used when the data is in an array
- TJSONArrayFieldMapper = Class(TJSONFieldMapper)
- procedure SetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row,Data : TJSONData); override;
- Function GetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row : TJSONData) : TJSONData; override;
- Function CreateRow : TJSONData; override;
- end;
- EJSONDataset = Class(EDatabaseError);
-
- implementation
- uses dateutils, jsonparser;
- type
- PRecInfo = ^TRecInfo;
- TRecInfo = record
- Index: Integer;
- Bookmark: Longint;
- BookmarkFlag: TBookmarkFlag;
- end;
- { TJSONFieldMapper }
- function TJSONFieldMapper.GetJSONDataForField(F: TField; Row: TJSONData
- ): TJSONData;
- begin
- // This supposes that Index is correct, i.e. the field positions have not been changed.
- Result:=GetJSONDataForField(F.FieldName,F.Index,Row);
- end;
- procedure TJSONFieldMapper.SetJSONDataForField(F: TField; Row,Data: TJSONData);
- begin
- SetJSONDataForField(F.FieldName,F.Index,Row,Data);
- end;
- { TJSONArrayDataSet }
- function TExtJSJSONArrayDataSet.CreateFieldMapper: TJSONFieldMapper;
- begin
- Result:=TJSONArrayFieldMapper.Create;
- end;
- { TJSONObjectDataSet }
- function TExtJSJSONObjectDataSet.CreateFieldMapper: TJSONFieldMapper;
- begin
- Result:=TJSONObjectFieldMapper.Create;
- end;
- { TJSONArrayFieldMapper }
- procedure TJSONArrayFieldMapper.SetJSONDataForField(const FieldName: String;
- FieldIndex: Integer; Row, Data: TJSONData);
- begin
- (Row as TJSONArray).Items[FieldIndex]:=Data;
- end;
- function TJSONArrayFieldMapper.GetJSONDataForField(Const FieldName: String;
- FieldIndex: Integer; Row: TJSONData): TJSONData;
- begin
- Result:=(Row as TJSONArray).Items[FieldIndex];
- end;
- function TJSONArrayFieldMapper.CreateRow: TJSONData;
- begin
- Result:=TJSONArray.Create;
- end;
- { TJSONObjectFieldMapper }
- procedure TJSONObjectFieldMapper.SetJSONDataForField(const FieldName: String;
- FieldIndex: Integer; Row, Data: TJSONData);
- begin
- (Row as TJSONObject).Elements[FieldName]:=Data;
- end;
- function TJSONObjectFieldMapper.GetJSONDataForField(const FieldName: String;
- FieldIndex: Integer; Row: TJSONData): TJSONData;
- begin
- Result:=(Row as TJSONObject).Elements[FieldName];
- end;
- function TJSONObjectFieldMapper.CreateRow: TJSONData;
- begin
- Result:=TJSONObject.Create;
- end;
- procedure TBaseJSONDataSet.SetMetaData(AValue: TJSONObject);
- begin
- CheckInActive;
- if FMetaData=AValue then
- Exit;
- If OwnsData then
- FreeAndNil(FMetaData);
- FMetaData:=AValue;
- end;
- procedure TBaseJSONDataSet.SetRows(AValue: TJSONArray);
- begin
- CheckInActive;
- if FRows=AValue then Exit;
- If OwnsData then
- FreeAndNil(FRows);
- FRows:=AValue;
- end;
- function TBaseJSONDataSet.AllocRecordBuffer: TRecordBuffer;
- begin
- Result := TRecordBuffer(StrAlloc(fRecordSize));
- end;
- // the next two are particularly ugly.
- procedure TBaseJSONDataSet.InternalInitRecord(Buffer: TRecordBuffer);
- begin
- FillChar(Buffer^, FRecordSize, 0);
- end;
- procedure TBaseJSONDataSet.FreeRecordBuffer (var Buffer: TRecordBuffer);
- begin
- StrDispose(pansichar(Buffer));
- end;
- procedure TBaseJSONDataSet.GetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
- begin
- PInteger(Data)^ := PRecInfo(Buffer)^.Bookmark;
- end;
- function TBaseJSONDataSet.GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag;
- begin
- Result := PRecInfo(Buffer)^.BookmarkFlag;
- end;
- function TBaseJSONDataSet.GetRecNo: Integer;
- begin
- Result := FCurrent + 1;
- end;
- procedure TBaseJSONDataSet.InternalInitFieldDefs;
- begin
- If Assigned(FMetaData) then
- MetaDataToFieldDefs;
- if (FieldDefs.Count=0) then
- Raise EJSONDataset.Create('No fields found');
- end;
- procedure TBaseJSONDataSet.FreeData;
- begin
- If FOwnsData then
- begin
- FreeAndNil(FRows);
- FreeAndNil(FMetaData);
- end;
- if (FCurrentList<>FDefaultList) then
- FreeAndNil(FCurrentList)
- else
- FCurrentList:=Nil;
- FreeAndNil(FDefaultList);
- FreeAndNil(FFieldMapper);
- FCurrentList:=Nil;
- end;
- procedure TBaseJSONDataSet.FillList;
- Var
- I : Integer;
- begin
- FDefaultList:=TFPList.Create;
- For I:=0 to FRows.Count-1 do
- FDefaultList.Add(FRows[i]);
- FCurrentList:=FDefaultList;
- end;
- Function TExtJSJSONDataSet.StringToFieldType(S : String) : TFieldType;
- begin
- if (s='int') then
- Result:=ftLargeInt
- else if (s='float') then
- Result:=ftFloat
- else if (s='boolean') then
- Result:=ftBoolean
- else if (s='date') then
- Result:=ftDateTime
- else if (s='string') or (s='auto') or (s='') then
- Result:=ftString
- else
- if MapUnknownToStringType then
- Result:=ftString
- else
- Raise EJSONDataset.CreateFmt('Unknown JSON data type : %s',[s]);
- end;
- Function TExtJSJSONDataSet.GetStringFieldLength(F : TJSONObject; AName : String; AIndex : Integer) : integer;
- Var
- I,L : Integer;
- D : TJSONData;
- begin
- Result:=0;
- I:=F.IndexOfName('maxlen');
- if (I<>-1) and (F.Items[I].jsonType=jtNumber) then
- begin
- Result:=StrToIntDef(trim(F.Items[i].AsString),-1);
- if (Result=-1) then
- Raise EJSONDataset.CreateFmt('Invalid maximum length specifier for field %s : %s',[AName,F.Items[i].AsString])
- end
- else
- begin
- For I:=0 to FRows.Count-1 do
- begin
- D:=FFieldMapper.GetJSONDataForField(Aname,AIndex,FRows[i]);
- if (D<>Nil) and (D.JsonType<>jtNull) then
- begin
- l:=Length(D.AsString);
- if L>Result then
- Result:=L;
- end;
- end;
- end;
- if (Result=0) then
- Result:=20;
- end;
- procedure TExtJSJSONDataSet.LoadFromStream(S: TStream);
- Var
- P : TJSONParser;
- D : TJSONData;
- O : TJSONObject;
- N : String;
- I : Integer;
- begin
- P:=TJSONParser.Create(S);
- try
- D:=P.Parse;
- try
- if (D.JSONType=jtObject) then
- O:=D as TJSONObject
- else
- begin
- FreeAndNil(D);
- Raise EJSONDataset.Create('Not a valid ExtJS JSON data packet');
- end;
- N:='rows';
- // Check metadata
- I:=O.IndexOfName('metaData');
- if (I<>-1) then
- begin
- If (O.Items[i].JSONType<>jtObject) then
- Raise EJSONDataset.Create('Invalid ExtJS JSON metaData in data packet.');
- Metadata:=O.Objects['metaData'];
- O.Extract(I);
- I:=Metadata.IndexOfName('root');
- If (I<>-1) then
- begin
- if (MetaData.Items[i].JSONType<>jtString) then
- Raise EJSONDataset.Create('Invalid ExtJS JSON root element in metaData.');
- N:=MetaData.Strings['root'];
- end;
- end;
- // Check rows
- I:=O.IndexOfName(N);
- if (I=-1) then
- Raise EJSONDataset.Create('Missing rows in data packet');
- if (O.Items[i].JSONType<>jtArray) then
- Raise EJSONDataset.Create('Rows element must be an array');
- Rows:=O.Items[i] as TJSONArray;
- O.Extract(I);
- OwnsData:=True;
- finally
- D.Free;
- end;
- finally
- P.Free;
- end;
- end;
- procedure TExtJSJSONDataSet.LoadFromFile(const AFileName: string);
- Var
- F : TFileStream;
- begin
- F:=TFileStream.Create(AFileName,fmOpenRead or fmShareDenyWrite);
- try
- LoadFromStream(F);
- finally
- F.Free;
- end;
- end;
- procedure TExtJSJSONDataSet.SaveToStream(S: TStream; SaveMetaData: Boolean);
- Var
- O : TJSONObject;
- SS : TStringStream;
- N : String;
- I : Integer;
- M : TJSONobject;
- begin
- O:=TJSONObject.Create;
- try
- N:='rows';
- If SaveMetaData then
- begin
- M:=MetaData;
- if M=Nil then
- M:=GenerateMetaData;
- O.Add('metaData',M);
- if M.IndexOfName('root')<>-1 then
- N:=M.Strings['root'];
- end;
- O.Add(N,Rows);
- SS:=TStringStream.Create(O.FormatJSON());
- try
- S.CopyFrom(SS,0);
- finally
- SS.Free;
- end;
- finally
- If (MetaData<>Nil) and SaveMetaData then
- begin
- I:=O.IndexOfName('metaData');
- if (I<>-1) then
- O.Extract(i);
- end;
- O.Extract(O.IndexOfName(N));
- O.Free;
- end;
- end;
- procedure TExtJSJSONDataSet.SaveToFile(const AFileName: String;
- SaveMetaData: Boolean);
- Var
- F : TFileStream;
- begin
- F:=TFileStream.Create(AFileName,fmCreate);
- try
- SaveToStream(F,SaveMetaData);
- finally
- F.Free;
- end;
- end;
- procedure TExtJSJSONDataSet.MetaDataToFieldDefs;
- Var
- A : TJSONArray;
- F : TJSONObject;
- I,J,FS : Integer;
- N,idf : String;
- ft: TFieldType;
- D : TJSONData;
- begin
- FieldDefs.Clear;
- I:=FMetadata.IndexOfName('fields');
- if (I=-1) or (FMetaData.Items[i].JSONType<>jtArray) then
- Raise EJSONDataset.Create('Invalid metadata object');
- A:=FMetadata.Arrays['fields'];
- For I:=0 to A.Count-1 do
- begin
- If (A.Types[i]<>jtObject) then
- Raise EJSONDataset.CreateFmt('Field definition %d in metadata (%s) is not an object',[i,A[i].AsJSON]);
- F:=A.Objects[i];
- J:=F.IndexOfName('name');
- If (J=-1) or (F.Items[J].JSONType<>jtString) then
- Raise EJSONDataset.CreateFmt('Field definition %d in has no or invalid name property',[i]);
- N:=F.Items[J].AsString;
- J:=F.IndexOfName('type');
- If (J=-1) then
- ft:=ftstring
- else If (F.Items[J].JSONType<>jtString) then
- Raise EJSONDataset.CreateFmt('Field definition %d in has invalid type property',[i])
- else
- ft:=StringToFieldType(F.Items[J].asString);
- if (ft=ftString) then
- fs:=GetStringFieldLength(F,N,I)
- else
- fs:=0;
- FieldDefs.Add(N,ft,fs);
- end;
- FFields:=A;
- end;
- function TExtJSJSONDataSet.GenerateMetaData: TJSONObject;
- Var
- F : TJSONArray;
- O : TJSONObject;
- I,M : Integer;
- T : STring;
- begin
- Result:=TJSONObject.Create;
- F:=TJSONArray.Create;
- Result.Add('fields',F);
- For I:=0 to FieldDefs.Count -1 do
- begin
- O:=TJSONObject.Create(['name',FieldDefs[i].name]);
- F.Add(O);
- M:=0;
- case FieldDefs[i].DataType of
- ftfixedwidechar,
- ftwideString,
- ftfixedchar,
- ftString:
- begin
- T:='string';
- M:=FieldDefs[i].Size;
- end;
- ftBoolean: T:='boolean';
- ftDate,
- ftTime,
- ftDateTime: T:='date';
- ftFloat: t:='float';
- ftSmallint,
- ftInteger,
- ftAutoInc,
- ftLargeInt,
- ftword: t:='int';
- else
- Raise EJSONDataset.CreateFmt('Unsupported field type : %s',[GetEnumName(TypeInfo(TFieldType),Ord(FieldDefs[i].DataType))]);
- end; // case
- O.Strings['type']:=t;
- if M<>0 then
- O.Integers['maxlen']:=M;
- end;
- Result.strings['root']:='rows';
- end;
- Function TExtJSJSONDataSet.ConvertDateFormat(S : String) : String;
- { Not handled: N S w z W t L o O P T Z c U MS }
- begin
- Result:=StringReplace(S,'y','yy',[rfReplaceall]);
- Result:=StringReplace(Result,'Y','yyyy',[rfReplaceall]);
- Result:=StringReplace(Result,'g','h',[rfReplaceall]);
- Result:=StringReplace(Result,'G','hh',[rfReplaceall]);
- Result:=StringReplace(Result,'F','mmmm',[rfReplaceall]);
- Result:=StringReplace(Result,'M','mmm',[rfReplaceall]);
- Result:=StringReplace(Result,'n','m',[rfReplaceall]);
- Result:=StringReplace(Result,'D','ddd',[rfReplaceall]);
- Result:=StringReplace(Result,'j','d',[rfReplaceall]);
- Result:=StringReplace(Result,'l','dddd',[rfReplaceall]);
- Result:=StringReplace(Result,'i','nn',[rfReplaceall]);
- Result:=StringReplace(Result,'u','zzz',[rfReplaceall]);
- Result:=StringReplace(Result,'a','am/pm',[rfReplaceall,rfIgnoreCase]);
- Result:=LowerCase(Result);
- end;
- procedure TExtJSJSONDataSet.InitDateTimeFields;
- Var
- F : TJSONObject;
- FF : TField;
- I,J : Integer;
- Fmt : String;
- begin
- If (FFields=Nil) then
- Exit;
- For I:=0 to FFields.Count-1 do
- begin
- F:=FFields.Objects[i];
- J:=F.IndexOfName('type');
- if (J<>-1) and (F.Items[J].JSONType=jtString) and (F.items[J].AsString='date') then
- begin
- J:=F.IndexOfName('dateFormat');
- if (J<>-1) and (F.Items[J].JSONType=jtString) then
- begin
- FMT:=ConvertDateFormat(F.Items[J].AsString);
- FF:=FindField(F.Strings['name']);
- if (FF<>Nil) and (FF.DataType in [ftDate,ftTime,ftDateTime]) and (FF.FieldKind=fkData) then
- begin
- if FF is TJSONDateField then
- TJSONDateField(FF).DateFormat:=Fmt
- else if FF is TJSONTimeField then
- TJSONTimeField(FF).TimeFormat:=Fmt
- else if FF is TJSONDateTimeField then
- TJSONDateTimeField(FF).DateTimeFormat:=Fmt;
- end;
- end;
- end;
- end;
- end;
- function TBaseJSONDataSet.GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode;
- DoCheck: Boolean): TGetResult;
- begin
- Result := grOK; // default
- case GetMode of
- gmNext: // move on
- if fCurrent < fCurrentList.Count - 1 then
- Inc (fCurrent)
- else
- Result := grEOF; // end of file
- gmPrior: // move back
- if fCurrent > 0 then
- Dec (fCurrent)
- else
- Result := grBOF; // begin of file
- gmCurrent: // check if empty
- if fCurrent >= fCurrentList.Count then
- Result := grEOF;
- end;
- if Result = grOK then // read the data
- with PRecInfo(Buffer)^ do
- begin
- Index := fCurrent;
- BookmarkFlag := bfCurrent;
- Bookmark := fCurrent;
- end;
- end;
- function TBaseJSONDataSet.GetRecordCount: Integer;
- begin
- Result := FCurrentList.Count;
- end;
- function TBaseJSONDataSet.GetRecordSize: Word;
- begin
- Result := SizeOf(Integer); // actual data without house-keeping
- end;
- procedure TBaseJSONDataSet.InternalClose;
- begin
- // disconnet and destroy field objects
- BindFields (False);
- if DefaultFields then
- DestroyFields;
- FreeData;
- end;
- procedure TBaseJSONDataSet.InternalDelete;
- Var
- R : TJSONData;
- begin
- R:=TJSONData(FCurrentList[FCurrent]);
- FCurrentList.Delete(FCurrent);
- if (FCurrent>=FCurrentList.Count) then
- Dec(FCurrent);
- FRows.Remove(R);
- end;
- procedure TBaseJSONDataSet.InternalFirst;
- begin
- FCurrent := -1;
- end;
- procedure TBaseJSONDataSet.InternalGotoBookmark(ABookmark: Pointer);
- begin
- if (ABookmark <> nil) then
- FCurrent := Integer (ABookmark);
- end;
- procedure TBaseJSONDataSet.InternalInsert;
- Var
- I : Integer;
- D : TFieldDef;
- begin
- FEditRow:=FFieldMapper.CreateRow;
- For I:=0 to FieldDefs.Count-1 do
- begin
- D:=FieldDefs[i];
- FFieldMapper.SetJSONDataForField(D.Name,D.Index,FEditRow,TJSONNull.Create);
- end;
- end;
- procedure TBaseJSONDataSet.InternalEdit;
- begin
- FEditRow:=TJSONData(FCurrentList[FCurrent]).Clone;
- end;
- procedure TBaseJSONDataSet.InternalCancel;
- begin
- FreeAndNil(FEditRow);
- end;
- procedure TBaseJSONDataSet.InternalLast;
- begin
- FCurrent:=FCurrentList.Count-1;
- end;
- procedure TBaseJSONDataSet.InitDateTimeFields;
- begin
- // Do nothing
- end;
- procedure TBaseJSONDataSet.InternalOpen;
- begin
- FreeAndNil(FFieldMapper);
- FFieldMapper:=CreateFieldMapper;
- IF (FRows=Nil) then // opening from fielddefs ?
- begin
- FRows:=TJSONArray.Create;
- OwnsData:=True;
- end;
- FillList;
- InternalInitFieldDefs;
- if DefaultFields then
- CreateFields;
- BindFields (True);
- InitDateTimeFields;
- FRecordSize := sizeof (TRecInfo);
- FCurrent := -1;
- BookmarkSize := sizeOf (Integer);
- end;
- procedure TBaseJSONDataSet.InternalPost;
- Var
- RI,I : integer;
- begin
- GetBookMarkData(ActiveBuffer,@I);
- if (State=dsInsert) then
- begin // Insert or Append
- FRows.Add(FEditRow);
- if GetBookMarkFlag(ActiveBuffer)=bfEOF then
- begin // Append
- FDefaultList.Add(FEditRow);
- if (FCurrentList<>FDefaultList) then
- FCurrentList.Add(FEditRow);
- end
- else // insert
- begin
- FCurrentList.Insert(FCurrent,FEditRow);
- if (FCurrentList<>FDefaultList) then
- FDefaultList.Add(FEditRow);
- end;
- end
- else
- begin // Edit
- RI:=FRows.IndexOf(TJSONData(FCurrentList[FCurrent]));
- if (RI<>-1) then
- FRows[RI]:=FEditRow
- else
- FRows.Add(FEditRow);
- FCurrentList[FCurrent]:=FEditRow;
- if (FCurrentList<>FDefaultList) then
- FDefaultList[FCurrent]:=FEditRow;
- end;
- FEditRow:=Nil;
- end;
- procedure TBaseJSONDataSet.InternalSetToRecord(Buffer: TRecordBuffer);
- begin
- FCurrent := PRecInfo(Buffer)^.Index;
- end;
- function TBaseJSONDataSet.GetFieldClass(FieldType: TFieldType): TFieldClass;
- begin
- case FieldType of
- ftDate : Result:=TJSONDateField;
- ftDateTime : Result:=TJSONDateTimeField;
- ftTime : Result:=TJSONTimeField;
- else
- Result:=inherited GetFieldClass(FieldType);
- end;
- end;
- function TBaseJSONDataSet.IsCursorOpen: Boolean;
- begin
- Result := Assigned(FDefaultList);
- end;
- procedure TBaseJSONDataSet.SetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
- begin
- PRecInfo(Buffer)^.Bookmark := PInteger(Data)^;
- end;
- function TBaseJSONDataSet.ConvertDateTimeField(S : String; F : TField) : TDateTime;
- Var
- Ptrn : string;
- begin
- Result:=0;
- Case F.DataType of
- ftDate : Ptrn:=TJSONDateField(F).DateFormat;
- ftTime : Ptrn:=TJSONTimeField(F).TimeFormat;
- ftDateTime : Ptrn:=TJSONDateTimeField(F).DateTimeFormat;
- end;
- If (Ptrn='') then
- Case F.DataType of
- ftDate : Result:=StrToDate(S);
- ftTime : Result:=StrToTime(S);
- ftDateTime : Result:=StrToDateTime(S);
- end
- else
- begin
- Result:=ScanDateTime(ptrn,S,1);
- end;
- end;
- function TBaseJSONDataSet.FormatDateTimeField(DT: TDateTime; F: TField
- ): String;
- Var
- Ptrn : string;
- begin
- Result:='';
- Case F.DataType of
- ftDate : Ptrn:=TJSONDateField(F).DateFormat;
- ftTime : Ptrn:=TJSONTimeField(F).TimeFormat;
- ftDateTime : Ptrn:=TJSONDateTimeField(F).DateTimeFormat;
- end;
- If (Ptrn='') then
- Case F.DataType of
- ftDate : Result:=DateToStr(DT);
- ftTime : Result:=TimeToStr(DT);
- ftDateTime : Result:=DateTimeToStr(DT);
- end
- else
- Result:=FormatDateTime(ptrn,DT);
- end;
- function TBaseJSONDataSet.GetFieldData(Field: TField; Buffer: Pointer
- ; NativeFormat : Boolean): Boolean;
- var
- R,F : TJSONData;
- B : WordBool;
- s: string;
- w : widestring;
- D : TDateTime;
- FV : Double;
- I : Longint;
- li : int64;
- begin
- I:=PRecInfo(ActiveBuffer)^.Index;
- // Writeln('Index : ',I,'<',FCurrentList.Count,' ?');
- if (I<>-1) then
- R:=TJSONData(FCurrentList[i])
- else
- R:=FEditRow;
- F:=FFieldMapper.GetJSONDataForField(Field,R);
- Result:=(F<>Nil) and not (F.JSONType in [jtUnknown,jtNull]);
- if not Result then
- exit;
- case Field.DataType of
- ftfixedwidechar,
- ftwideString:
- begin
- W:=F.AsString;
- if (length(W)>0) then
- Move(W[1],Buffer^,Length(W)*SizeOf(Widechar)+1)
- else
- PChar(Buffer)^:=#0;
- end;
- ftfixedchar,
- ftString:
- begin
- S:=F.AsString;
- if (length(s)>0) then
- Move(S[1],Buffer^,Length(S)+1)
- else
- PChar(Buffer)^:=#0;
- end;
- ftBoolean:
- begin
- B:=F.AsBoolean;
- Move(B,Buffer^,sizeof(WordBool));
- end;
- ftDate,
- ftTime,
- ftDateTime:
- begin
- D:=ConvertDateTimeField(F.AsString,Field);
- Move(D,Buffer^,sizeof(TDateTime));
- end;
- ftFloat:
- begin
- Fv:=F.asFloat;
- Move(FV,Buffer^,sizeof(Double));
- end;
- ftSmallint,
- ftInteger,
- ftAutoInc,
- ftword:
- begin
- I:=F.AsInteger;
- Move(I,Buffer^,SizeOf(I));
- end;
- ftLargeint:
- begin
- LI:=F.AsInt64;
- Move(LI,Buffer^,SizeOf(LI));
- end;
- else
- Raise EJSONDataset.CreateFmt('Unsupported field type : %s',[GetEnumName(TypeInfo(TFieldType),Ord(Field.DataType))]);
- end; // case
- end;
- procedure TBaseJSONDataSet.SetFieldData(Field: TField; Buffer: Pointer; NativeFormat : Boolean);
- var
- R,F : TJSONData;
- B : PWordBool;
- s: string;
- w : widestring;
- D : TDateTime;
- FV : Double;
- I : Longint;
- li : int64;
- begin
- F:=Nil;
- if (Buffer<>nil) then
- case Field.DataType of
- ftfixedwidechar,
- ftwideString:
- begin
- SetLength(W,Field.Size);
- if (length(W)>0) then
- Move(Buffer^,W[1],Field.Size*SizeOf(Widechar));
- F:=TJSONString.Create(W);
- end;
- ftfixedchar,
- ftString:
- F:=TJSONString.Create(StrPas(Buffer));
- ftBoolean:
- F:=TJSONBoolean.Create(PWordBool(Buffer)^);
- ftDate,
- ftTime,
- ftDateTime:
- begin
- S:=FormatDateTimeField(PDateTime(Buffer)^,Field);
- F:=TJSONString.Create(S);
- end;
- ftFloat:
- F:=TJSONFloatNumber.Create(PDouble(Buffer)^);
- ftSmallint,
- ftInteger,
- ftAutoInc,
- ftword:
- F:=TJSONIntegerNumber.Create(PLongint(Buffer)^);
- ftLargeint:
- begin
- F:=TJSONInt64Number.Create(PInt64(Buffer)^);
- end;
- else
- Raise EJSONDataset.CreateFmt('Unsupported field type : %s',[GetEnumName(TypeInfo(TFieldType),Ord(Field.DataType))]);
- end; // case
- if (F=Nil) then
- F:=TJSONNull.Create;
- // Writeln('Set field data : ',F.AsJSON);
- FFieldMapper.SetJSONDataForField(Field,FEditRow,F);
- // Writeln('Field data is set : ',FEditRow.AsJSON);
- end;
- procedure TBaseJSONDataSet.SetBookmarkFlag(Buffer: TRecordBuffer;
- Value: TBookmarkFlag);
- begin
- PRecInfo(Buffer)^.BookmarkFlag := Value;
- end;
- procedure TBaseJSONDataSet.SetRecNo(Value: Integer);
- begin
- if (Value < 0) or (Value > FCurrentList.Count) then
- raise EJSONDataset.CreateFmt('SetRecNo: index %d out of range',[Value]);
- FCurrent := Value - 1;
- Resync([]);
- DoAfterScroll;
- end;
- constructor TBaseJSONDataSet.Create(AOwner: TComponent);
- begin
- inherited;
- FownsData:=True;
- end;
- destructor TBaseJSONDataSet.Destroy;
- begin
- FreeData;
- inherited;
- end;
- end.