Unexpected token 'o', "[object Blob]" is not valid JSON

This is the code i am using to load the data into my TXDataWebDataSet
(i found this in the support center)

function TfrmCustomerInfoMain.DoLoadInitialData: Boolean;
var Response: TXDataClientResponse;
var JFDBS: TJSObject;
var JManager: TJSObject;
var JTableList: TJSObject;
var JRowList: TJSArray;

begin
wclDesign.Items.Clear;
wclMaterial.Items.Clear;

Await(DoServerLogin);

tblMaterial.Load;
tblDesign.Load;

tblArtChargenInfo.Close;

Response := Await(XDataClient.RawInvokeAsync('IDataServices.GetCustomerItemList', ['SELECT * FROM ARTIKELDATEN']));

JFDBS := TJSObject(TJSObject(TJSJson.Parse(string(Response.Result)))['FDBS']);

if Assigned(JFDBS) then
begin
JManager := TJSObject(JFDBS['Manager']);
JTableList := TJSObject(TJSArray(JManager['TableList'])[0]);
JRowList := TJSArray(JTableList['RowList']);
console.Log(JRowList);
end else
begin
console.Log('nothing assigned');
end;

tblArtChargenInfo.SetJsonData(JRowList);
tblArtChargenInfo.Open;

UpdateGUI;
end;

This is the way the data is produced in my Xdata server :slight_smile:
procedure TDBController.GetCustomerItemList(aStream: TStream; aSQLStatement:
string);
begin
if Assigned(aStream) and (aSQLStatement <> EmptyStr) then
begin
FDQuery.Close;
FDQuery.SQL.Clear;
FDQuery.SQL.Add(aSQLStatement);
FDQuery.Open();
if not FDQuery.IsEmpty then
begin
FDQuery.SaveToStream(aStream,sfJSON);
end;
end;
end;

this is the result i get when i call the function in a browser :
(just a snippet)

{"FDBS":{"Version":16,"Manager":{"UpdatesRegistry":true,"TableList":[{"class":"Table","Name":"FDQuery","SourceName":"ARTIKELDATEN","SourceID":1,"TabID":0,"EnforceConstraints":false,"MinimumCapacity":50,"ColumnList":[{"class":"Column","Name":"AT_ARTIKEL_ID","SourceName":"AT_ARTIKEL_ID","SourceID":1,"DataType":"FmtBCD","Precision":15,"Searchable":true,"Base":true,"OInUpdate":true,"OInWhere":true,"OInKey":true,"OriginColName":"AT_ARTIKEL_ID","SourcePrecision":15,"SourceSize":22},{"class":"Column","Name":"AT_WERKSTOFF","SourceName":"AT_WERKSTOFF","SourceID":2,"DataType":"AnsiString","Size":25,"Searchable":true,"Base":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_WERKSTOFF","SourceSize":25},{"class":"Column","Name":"AT_BAUFORM","SourceName":"AT_BAUFORM","SourceID":3,"DataType":"AnsiString","Size":25,"Searchable":true,"Base":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_BAUFORM","SourceSize":25},{"class":"Column","Name":"AT_BREITE","SourceName":"AT_BREITE","SourceID":4,"DataType":"BCD","Precision":15,"Scale":4,"Searchable":true,"Base":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_BREITE","SourcePrecision":15,"SourceScale":4,"SourceSize":22},{"class":"Column","Name":"AT_LAENGE","SourceName":"AT_LAENGE","SourceID":5,"DataType":"BCD","Precision":15,"Scale":4,"Searchable":true,"Base":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_LAENGE","SourcePrecision":15,"SourceScale":4,"SourceSize":22},{"class":"Column","Name":"AT_ARTIKELNUMMER","SourceName":"AT_ARTIKELNUMMER","SourceID":6,"DataType":"AnsiString","Size":30,"Searchable":true,"AllowNull":true,"Base":true,"OAllowNull":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_ARTIKELNUMMER","SourceSize":30},{"class":"Column","Name":"AT_SUFFIX","SourceName":"AT_SUFFIX","SourceID":7,"DataType":"AnsiString","Size":5,"Searchable":true,"AllowNull":true,"Base":true,"OAllowNull":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_SUFFIX","SourceSize":5},{"class":"Column","Name":"AT_BEZEICHNUNG1","SourceName":"AT_BEZEICHNUNG1","SourceID":8,"DataType":"AnsiString","Size":100,"Searchable":true,"AllowNull":true,"Base":true,"OAllowNull":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_BEZEICHNUNG1","SourceSize":100},{"class":"Column","Name":"AT_BEZEICHNUNG2","SourceName":"AT_BEZEICHNUNG2","SourceID":9,"DataType":"AnsiString","Size":100,"Searchable":true,"AllowNull":true,"Base":true,"OAllowNull":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_BEZEICHNUNG2","SourceSize":100},{"class":"Column","Name":"AT_EAN_CODE","SourceName":"AT_EAN_CODE","SourceID":10,"DataType":"AnsiString","Size":15,"Searchable":true,"AllowNull":true,"Base":true,"OAllowNull":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_EAN_CODE","SourceSize":15},{"class":"Column","Name":"AT_BESCHREIBUNG","SourceName":"AT_BESCHREIBUNG","SourceID":11,"DataType":"AnsiString","Size":500,"Searchable":true,"AllowNull":true,"Base":true,"OAllowNull":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_BESCHREIBUNG","SourceSize":500},{"class":"Column","Name":"AT_ABRIEB","SourceName":"AT_ABRIEB","SourceID":12,"DataType":"AnsiString","Size":10,"Searchable":true,"AllowNull":true,"Base":true,"OAllowNull":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_ABRIEB","SourceSize":10},{"class":"Column","Name":"AT_DIN_NORM","SourceName":"AT_DIN_NORM","SourceID":13,"DataType":"AnsiString","Size":10,"Searchable":true,"AllowNull":true,"Base":true,"OAllowNull":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_DIN_NORM","SourceSize":10},{"class":"Column","Name":"AT_FROSTSICHER_JN","SourceName":"AT_FROSTSICHER_JN","SourceID":14,"DataType":"AnsiString","Size":1,"Searchable":true,"AllowNull":true,"FixedLen":true,"Base":true,"OAllowNull":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_FROSTSICHER_JN","SourceSize":1},{"class":"Column","Name":"AT_RUTSCHFEST_JN","SourceName":"AT_RUTSCHFEST_JN","SourceID":15,"DataType":"AnsiString","Size":1,"Searchable":true,"AllowNull":true,"FixedLen":true,"Base":true,"OAllowNull":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_RUTSCHFEST_JN","SourceSize":1},{"class":"Column","Name":"AT_ZEUGNIS_JN","SourceName":"AT_ZEUGNIS_JN","SourceID":16,"DataType":"AnsiString","Size":1,"Searchable":true,"AllowNull":true,"FixedLen":true,"Base":true,"OAllowNull":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_ZEUGNIS_JN","SourceSize":1},{"class":"Column","Name":"AT_WEBSHOP_JN","SourceName":"AT_WEBSHOP_JN","SourceID":17,"DataType":"AnsiString","Size":1,"Searchable":true,"AllowNull":true,"FixedLen":true,"Base":true,"OAllowNull":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_WEBSHOP_JN","SourceSize":1},{"class":"Column","Name":"AT_AKTIV_JN","SourceName":"AT_AKTIV_JN","SourceID":18,"DataType":"AnsiString","Size":1,"Searchable":true,"AllowNull":true,"FixedLen":true,"Base":true,"OAllowNull":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"AT_AKTIV_JN","SourceSize":1},

Where is my error ????

br
Michael

I haven't checked this yet, and I'm not 100% sure it is the solution to your problem, but XData introduced a breaking change in 5.8 that likely needs to be factored in, as the example you're referencing was written well before then. I'm guessing that getting a string from the call to RawInvokeAsync might be the culprit.

See the end of this thread for the change.

I ask you back: where is the error? :slight_smile:
I didn't understand what's wrong, exactly? You are invoking a XData service and it's returning data.

However, I have some comments about it:

  1. Your service receives a TStream as a parameter, but doesn't return any TStream. I don't even know how this is even working. The input TStream parameter should be the request body, and I can't see how you are returning the response body.

  2. I highly recommend you don't accept arbitrary SQL statements in a XData application. It should be very, very easy for a user to inspect the browser, get an access token, and then execute statements like DELETE * FROM <anytable> in your API.

Good Morning,

thx for the replay,

Point 1, i do return a Stream in my Service call, sorry i posted the wrong code snippet....
Point 2, i know that , this is only for testing , the issue of SQL injection is well known. Thx for the input anyway.

I solved the problem,

br
Michael

If you get a chance, can you post the solution?

1 Like

Hi Andrew,

yes of course no problem :cowboy_hat_face:

br
Michael

2 Likes

Always please post the solution so others can learn.

Thank you!

1 Like

Dear all,

late, but here is my solution...

function TfrmCustomerInfoMain.GetQueryData(const AEndpoint, AFilter,
ARequestTag: String; var ADataset: TXDataWebDataSet): Integer;
var LClient : TXDataWebClient;
var LResponse : TXDataClientResponse;
var LJFDBS : TJSObject;
var LJManager : TJSObject;
var LJTableList : TJSObject;
var LJColumnList : TJSArray;
var LJRowList : TJSArray;
var StringFields : Array of TStringField;
var IntegerFields : Array of TIntegerField;
var DateFields : Array of TDateTimeField;
var FloatFields : Array of TFloatField;
var LJsText : string;
var LBlob : JSValue;
var LIdx : Integer;
begin
Result := -1;
LClient := TXDataWebClient.Create(nil);
LClient.Connection := XDataWebConnection;

try
LResponse := Await(LClient.RawInvokeAsync(AEndpoint, [AFilter, ARequestTag]));
except
on Error: Exception do
begin
LClient.Free;
console.log('...something went wrong...');
exit;
end;
end;

LBlob := LResponse.Result;
asm
LJsText = await (LBlob.text());
end;

// Process the FireDAC-specific JSON that was returned
LJFDBS := TJSObject(TJSObject(TJSJson.Parse(LJsText))['FDBS']);
LJManager := TJSObject(LJFDBS['Manager']);
LJTableList := TJSObject(TJSArray(LJManager['TableList'])[0]);
LJColumnList := TJSArray(LJTableList['ColumnList']);
LJRowList := TJSArray(LJTableList['RowList']);

// console.log(LJFDBS);
// console.log(LJManager);
// console.log(LJTableList);

// Don't really want 'Original' in field names, so let's remove it from JSON first
// Probably a better one-liner, but this seems to work
for LIdx := 0 to LJRowList.Length - 1 do
begin
LJRowList.Elements[LIdx] := TJSObject(LJRowList.Elements[LIdx])['Original'];
end;

// We're assuming ADataset parameter is newly created and empty.
// First, add all the fields from JSON
// NOTE: Very likely more datatypes need to be added here
for LIdx := 0 to LJColumnList.Length - 1 do
begin
if (String(TJSObject(LJColumnList.Elements[LIdx])['DataType']) = 'AnsiString') then
begin
// NOTE: Different datatypes may need different values set (eg: Size for strings)
SetLength(StringFields, Length(StringFields) + 1);
StringFields[Length(StringFields)-1] := TStringField.Create(ADataset);
StringFields[Length(StringFields)-1].FieldName := String(TJSObject(LJColumnList.Elements[LIdx])['Name']);
StringFields[Length(StringFields)-1].Size := Integer(TJSObject(LJColumnList.Elements[LIdx])['Size']);
StringFields[Length(StringFields)-1].Dataset := ADataset;
end
else if (String(TJSObject(LJColumnList.Elements[LIdx])['DataType']) = 'Int32') then
begin
SetLength(IntegerFields, Length(IntegerFields) + 1);
IntegerFields[Length(IntegerFields)-1] := TIntegerField.Create(ADataset);
IntegerFields[Length(IntegerFields)-1].FieldName := String(TJSObject(LJColumnList.Elements[LIdx])['Name']);
IntegerFields[Length(IntegerFields)-1].Dataset := ADataset;
end
else if (String(TJSObject(LJColumnList.Elements[LIdx])['DataType']) = 'BCD') then
begin
SetLength(FloatFields, Length(FloatFields) + 1);
FloatFields[Length(FloatFields)-1] := TFloatField.Create(ADataset);
FloatFields[Length(FloatFields)-1].FieldName := String(TJSObject(LJColumnList.Elements[LIdx])['Name']);
FloatFields[Length(FloatFields)-1].Dataset := ADataset;
end
else if (String(TJSObject(LJColumnList.Elements[LIdx])['DataType']) = 'FmtBCD') then
begin
SetLength(FloatFields, Length(FloatFields) + 1);
FloatFields[Length(FloatFields)-1] := TFloatField.Create(ADataset);
FloatFields[Length(FloatFields)-1].FieldName := String(TJSObject(LJColumnList.Elements[LIdx])['Name']);
FloatFields[Length(FloatFields)-1].Dataset := ADataset;
end
else if (String(TJSObject(LJColumnList.Elements[LIdx])['DataType']) = 'DateTime') then
begin
SetLength(DateFields, Length(DateFields) + 1);
DateFields[Length(DateFields)-1] := TDateTimeField.Create(ADataset);
DateFields[Length(DateFields)-1].FieldName := String(TJSObject(LJColumnList.Elements[LIdx])['Name']);
DateFields[Length(DateFields)-1].Dataset := ADataset;
end
else
begin
console.log('ERROR: Field [' + String(TJSObject(LJColumnList.Elements[LIdx])['Name'])+'] has an unexpected datatype [' + String(TJSObject(LJColumnList.Elements[LIdx])['DataType'])+']');
end;
end;
// Add the data and return the ADataset as opened
ADataset.SetJSONData(LJRowList);
ADataset.Open;
Result := ADataset.RecordCount;
if Assigned(LClient) then
begin
FreeAndNil(LClient);
end;
end;

Br
Michael

1 Like