|
@@ -12,7 +12,7 @@ type
|
|
|
{ TJSONFieldMapper }
|
|
|
// This class is responsible for mapping the field objects of the records.
|
|
|
TJSONFieldMapper = Class(TObject)
|
|
|
- Protected
|
|
|
+ Public
|
|
|
// Return row TJSONData instance with data for field 'FieldName' or 'FieldIndex'.
|
|
|
Function GetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row : JSValue) : JSValue; virtual; abstract;
|
|
|
// Same, but now based on TField.
|
|
@@ -128,6 +128,8 @@ type
|
|
|
Property MetaData : TJSObject Read FMetaData Write SetMetaData;
|
|
|
// Rows
|
|
|
Property Rows : TJSArray Read FRows Write SetRows;
|
|
|
+ // Fieldmapper
|
|
|
+ Property FieldMapper : TJSONFieldMapper Read FFieldMapper;
|
|
|
public
|
|
|
constructor Create (AOwner: TComponent); override;
|
|
|
destructor Destroy; override;
|
|
@@ -161,50 +163,6 @@ type
|
|
|
property OnPostError;
|
|
|
end;
|
|
|
|
|
|
- { TExtJSJSONDataSet }
|
|
|
-
|
|
|
- // Base for ExtJS datasets. It handles MetaData conversion.
|
|
|
- TExtJSJSONDataSet = Class(TBaseJSONDataset)
|
|
|
- Private
|
|
|
- FFields : TJSArray;
|
|
|
- FIDField: String;
|
|
|
- FRoot: String;
|
|
|
- Protected
|
|
|
- // Data proxy support
|
|
|
- Procedure InternalOpen; override;
|
|
|
- function DoResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean; override;
|
|
|
- Function DataPacketReceived(ARequest: TDataRequest) : Boolean; override;
|
|
|
- Function GenerateMetaData : TJSObject;
|
|
|
- function ConvertDateFormat(S: String): String; virtual;
|
|
|
- Procedure MetaDataToFieldDefs; override;
|
|
|
- procedure InitDateTimeFields; override;
|
|
|
- function StringToFieldType(S: String): TFieldType;virtual;
|
|
|
- function GetStringFieldLength(F: TJSObject; AName: String; AIndex: Integer): integer; virtual;
|
|
|
- Public
|
|
|
- // 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;
|
|
|
- // Root of data array in data packet
|
|
|
- property Root : String Read FRoot Write FRoot;
|
|
|
- // property IDField
|
|
|
- property IDField : String Read FIDField Write FIDField;
|
|
|
- end;
|
|
|
-
|
|
|
- { TExtJSJSONObjectDataSet }
|
|
|
- // Use this dataset for data where the data is an array of objects.
|
|
|
- TExtJSJSONObjectDataSet = Class(TExtJSJSONDataSet)
|
|
|
- Protected
|
|
|
- Function CreateFieldMapper : TJSONFieldMapper; override;
|
|
|
- end;
|
|
|
-
|
|
|
- { TExtJSJSONArrayDataSet }
|
|
|
- // Use this dataset for data where the data is an array of arrays.
|
|
|
- TExtJSJSONArrayDataSet = Class(TExtJSJSONDataSet)
|
|
|
- Protected
|
|
|
- Function CreateFieldMapper : TJSONFieldMapper; override;
|
|
|
- end;
|
|
|
-
|
|
|
{ TJSONObjectFieldMapper }
|
|
|
// Fieldmapper to be used when the data is in an object
|
|
|
TJSONObjectFieldMapper = Class(TJSONFieldMapper)
|
|
@@ -243,19 +201,6 @@ 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 }
|
|
|
|
|
@@ -374,7 +319,7 @@ procedure TBaseJSONDataSet.FreeData;
|
|
|
begin
|
|
|
If FOwnsData then
|
|
|
begin
|
|
|
- FRows:=Nil
|
|
|
+ FRows:=Nil;
|
|
|
FMetaData:=Nil;
|
|
|
end;
|
|
|
if (FCurrentList<>FDefaultList) then
|
|
@@ -405,295 +350,7 @@ begin
|
|
|
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 : TJSObject; AName : String; AIndex : Integer) : integer;
|
|
|
-
|
|
|
-Var
|
|
|
- I,L : Integer;
|
|
|
- D : JSValue;
|
|
|
-
|
|
|
-begin
|
|
|
- Result:=0;
|
|
|
- D:=F.Properties['maxlen'];
|
|
|
- if Not jsIsNan(toNumber(D)) then
|
|
|
- begin
|
|
|
- Result:=Trunc(toNumber(D));
|
|
|
- if (Result<=0) then
|
|
|
- Raise EJSONDataset.CreateFmt('Invalid maximum length specifier for field %s',[AName])
|
|
|
- end
|
|
|
- else
|
|
|
- begin
|
|
|
- For I:=0 to FRows.Length-1 do
|
|
|
- begin
|
|
|
- D:=FFieldMapper.GetJSONDataForField(Aname,AIndex,FRows[i]);
|
|
|
- if isString(D) then
|
|
|
- begin
|
|
|
- l:=Length(String(D));
|
|
|
- if L>Result then
|
|
|
- Result:=L;
|
|
|
- end;
|
|
|
- end;
|
|
|
- end;
|
|
|
- if (Result=0) then
|
|
|
- Result:=20;
|
|
|
-end;
|
|
|
-
|
|
|
-procedure TExtJSJSONDataSet.MetaDataToFieldDefs;
|
|
|
-
|
|
|
-Var
|
|
|
- A : TJSArray;
|
|
|
- F : TJSObject;
|
|
|
- I,J,FS : Integer;
|
|
|
- N,idf : String;
|
|
|
- ft: TFieldType;
|
|
|
- D : JSValue;
|
|
|
-
|
|
|
-begin
|
|
|
- FieldDefs.Clear;
|
|
|
- D:=FMetadata.Properties['fields'];
|
|
|
- if Not IsArray(D) then
|
|
|
- Raise EJSONDataset.Create('Invalid metadata object');
|
|
|
- A:=TJSArray(D);
|
|
|
- For I:=0 to A.Length-1 do
|
|
|
- begin
|
|
|
- If Not isObject(A[i]) then
|
|
|
- Raise EJSONDataset.CreateFmt('Field definition %d in metadata is not an object',[i]);
|
|
|
- F:=TJSObject(A[i]);
|
|
|
- D:=F.Properties['name'];
|
|
|
- If Not isString(D) then
|
|
|
- Raise EJSONDataset.CreateFmt('Field definition %d in has no or invalid name property',[i]);
|
|
|
- N:=String(D);
|
|
|
- D:=F.Properties['type'];
|
|
|
- If IsNull(D) or isUndefined(D) then
|
|
|
- ft:=ftstring
|
|
|
- else If Not isString(D) then
|
|
|
- begin
|
|
|
- Raise EJSONDataset.CreateFmt('Field definition %d in has invalid type property',[i])
|
|
|
- end
|
|
|
- else
|
|
|
- begin
|
|
|
- ft:=StringToFieldType(String(D));
|
|
|
- end;
|
|
|
- if (ft=ftString) then
|
|
|
- fs:=GetStringFieldLength(F,N,I)
|
|
|
- else
|
|
|
- fs:=0;
|
|
|
- FieldDefs.Add(N,ft,fs);
|
|
|
- end;
|
|
|
- FFields:=A;
|
|
|
-end;
|
|
|
-
|
|
|
-procedure TExtJSJSONDataSet.InternalOpen;
|
|
|
|
|
|
-Var
|
|
|
- I : integer;
|
|
|
-
|
|
|
-begin
|
|
|
- inherited InternalOpen;
|
|
|
- Writeln('Checking ID field ',IDField, ' as key field');
|
|
|
- for I:=0 to Fields.Count-1 do
|
|
|
- If SameText(Fields[i].FieldName,IDField) then
|
|
|
- begin
|
|
|
- Fields[i].ProviderFlags:=Fields[i].ProviderFlags+[pfInKey];
|
|
|
- Writeln('Setting ID field ',IDField, ' as key field');
|
|
|
- end;
|
|
|
-end;
|
|
|
-
|
|
|
-function TExtJSJSONDataSet.DoResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean;
|
|
|
-
|
|
|
-Var
|
|
|
- D : JSValue;
|
|
|
- O : TJSObject;
|
|
|
- A : TJSArray;
|
|
|
- I,RecordIndex : Integer;
|
|
|
- FN : String;
|
|
|
-
|
|
|
-begin
|
|
|
- Result:=True;
|
|
|
- if anUpdate.OriginalStatus=usDeleted then
|
|
|
- exit;
|
|
|
- D:=anUpdate.ServerData;
|
|
|
- If isNull(D) then
|
|
|
- exit;
|
|
|
- if not isNumber(AnUpdate.Bookmark.Data) then
|
|
|
- exit(False);
|
|
|
- RecordIndex:=Integer(AnUpdate.Bookmark.Data);
|
|
|
- If isString(D) then
|
|
|
- O:=TJSOBject(TJSJSON.Parse(String(D)))
|
|
|
- else if isObject(D) then
|
|
|
- O:=TJSOBject(D)
|
|
|
- else
|
|
|
- Exit(False);
|
|
|
- if Not isArray(O[Root]) then
|
|
|
- exit(False)
|
|
|
- A:=TJSArray(O[Root]);
|
|
|
- If A.Length=1 then
|
|
|
- begin
|
|
|
- O:=TJSObject(A[0]);
|
|
|
- For I:=0 to Fields.Count-1 do
|
|
|
- begin
|
|
|
- if O.hasOwnProperty(Fields[i].FieldName) then
|
|
|
- Self.FFieldMapper.SetJSONDataForField(Fields[i],Rows[RecordIndex],O[FN]);
|
|
|
- end;
|
|
|
- end;
|
|
|
-end;
|
|
|
-
|
|
|
-function TExtJSJSONDataSet.DataPacketReceived(ARequest: TDataRequest): Boolean;
|
|
|
-
|
|
|
-Var
|
|
|
- O : TJSObject;
|
|
|
- A : TJSArray;
|
|
|
-
|
|
|
-begin
|
|
|
- Result:=False;
|
|
|
- If isNull(aRequest.Data) then
|
|
|
- exit;
|
|
|
- If isString(aRequest.Data) then
|
|
|
- O:=TJSOBject(TJSJSON.Parse(String(aRequest.Data)))
|
|
|
- else if isObject(aRequest.Data) then
|
|
|
- O:=TJSOBject(aRequest.Data)
|
|
|
- else
|
|
|
- DatabaseError('Cannot handle data packet');
|
|
|
- if (Root='') then
|
|
|
- root:='rows';
|
|
|
- if (IDField='') then
|
|
|
- idField:='id';
|
|
|
- if O.hasOwnProperty('metaData') and isObject(o['metaData']) then
|
|
|
- begin
|
|
|
- if not Active then // Load fields from metadata
|
|
|
- metaData:=TJSObject(o['metaData']);
|
|
|
- // We must always check this one...
|
|
|
- if metaData.hasOwnProperty('root') and isString(metaData['root']) then
|
|
|
- Root:=string(metaData['root']);
|
|
|
- if metaData.hasOwnProperty('idField') and isString(metaData['idField']) then
|
|
|
- IDField:=string(metaData['idField']);
|
|
|
- end;
|
|
|
- if O.hasOwnProperty(Root) and isArray(o[Root]) then
|
|
|
- begin
|
|
|
- A:=TJSArray(o[Root]);
|
|
|
- Result:=A.Length>0;
|
|
|
- AddToRows(A);
|
|
|
- end;
|
|
|
-end;
|
|
|
-
|
|
|
-function TExtJSJSONDataSet.GenerateMetaData: TJSObject;
|
|
|
-
|
|
|
-Var
|
|
|
- F : TJSArray;
|
|
|
- O : TJSObject;
|
|
|
- I,M : Integer;
|
|
|
- T : STring;
|
|
|
-
|
|
|
-begin
|
|
|
- Result:=TJSObject.New;
|
|
|
- F:=TJSArray.New;
|
|
|
- Result.Properties['fields']:=F;
|
|
|
- For I:=0 to FieldDefs.Count -1 do
|
|
|
- begin
|
|
|
- O:=New(['name',FieldDefs[i].name]);
|
|
|
- F.push(O);
|
|
|
- M:=0;
|
|
|
- case FieldDefs[i].DataType of
|
|
|
- ftfixedchar,
|
|
|
- ftString:
|
|
|
- begin
|
|
|
- T:='string';
|
|
|
- M:=FieldDefs[i].Size;
|
|
|
- end;
|
|
|
- ftBoolean: T:='boolean';
|
|
|
- ftDate,
|
|
|
- ftTime,
|
|
|
- ftDateTime: T:='date';
|
|
|
- ftFloat: t:='float';
|
|
|
- ftInteger,
|
|
|
- ftAutoInc,
|
|
|
- ftLargeInt : t:='int';
|
|
|
- else
|
|
|
- Raise EJSONDataset.CreateFmt('Unsupported field type : %s',[Ord(FieldDefs[i].DataType)]);
|
|
|
- end; // case
|
|
|
- O.Properties['type']:=t;
|
|
|
- if M<>0 then
|
|
|
- O.Properties['maxlen']:=M;
|
|
|
- end;
|
|
|
- Result.Properties['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 : TJSObject;
|
|
|
- FF : TField;
|
|
|
- I,J : Integer;
|
|
|
- Fmt : String;
|
|
|
- D : JSValue;
|
|
|
-
|
|
|
-begin
|
|
|
- If (FFields=Nil) then
|
|
|
- Exit;
|
|
|
- For I:=0 to FFields.Length-1 do
|
|
|
- begin
|
|
|
- F:=TJSObject(FFields[i]);
|
|
|
- D:=F.Properties['type'];
|
|
|
- if isString(D) and (String(D)='date') then
|
|
|
- begin
|
|
|
- D:=F.Properties['dateFormat'];
|
|
|
- if isString(D) then
|
|
|
- begin
|
|
|
- FMT:=ConvertDateFormat(String(D));
|
|
|
- FF:=FindField(String(F.Properties['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(Var Buffer: TDataRecord; GetMode: TGetMode; DoCheck: Boolean): TGetResult;
|
|
|
begin
|