Blog
All Blog Posts | Next Post | Previous Post
Extend TMS WEB Core with JS Libraries with Andrew: Epic JSON Primer (part 2)
Bookmarks:
Friday, May 6, 2022

Here is part 2 of "Extend TMS WEB Core with JS Libraries with Andrew: Epic JSON Primer"
20 : Relative Performance.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Object: TJSObject; PAS_Object: TJSONObject; ElapsedTime: TDateTime; i: Integer; Count: Integer; begin ElapsedTime := Now; // JS Create 1,000,000 Objects asm var JS_Object = {}; for (var i = 0; i <= 999999; i++) { JS_Object['test '+i] = 'test '+i; } end; console.log('JS Create: '+IntToStr(MillisecondsBetween(Now,ElapsedTime))+' ms'); ElapsedTime := Now; // JS Count Objects asm Count = Object.keys(JS_Object).length; end; console.log('JS Count: '+IntToStr(MillisecondsBetween(Now,ElapsedTime))+' ms'); ElapsedTime := Now; // WC Create 1,000,000 Objects WC_Object := TJSObject.new; for i := 0 to 999999 do WC_Object['test '+IntToStr(i)] := 'test '+IntToStr(i); console.log('WC Create: '+IntToStr(MillisecondsBetween(Now,ElapsedTime))+' ms'); ElapsedTime := Now; // WC Count Objects Count := length(TJSObject.keys(WC_Object)); console.log('WC Count: '+IntToStr(MillisecondsBetween(Now,ElapsedTime))+' ms'); ElapsedTime := Now; // PAS Create 10,000 Objects PAS_Object := TJSONObject.Create; for i := 0 to 9999 do PAS_Object.AddPair('test '+IntToStr(i),'test '+IntToStr(i)); console.log('PAS Create: '+IntToStr(MillisecondsBetween(Now,ElapsedTime))+' ms'); ElapsedTime := Now; // PAS Count Objects Count := PAS_Object.Count; console.log('PAS Count: '+IntToStr(MillisecondsBetween(Now,ElapsedTime))+' ms'); end; console.log output: JS Create: 1154 ms JS Count: 301 ms WC Create: 1006 ms WC Count: 272 ms PAS Create: 27439 ms PAS Count: 0 ms
21 : Iterate through JSON Object Elements.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Object: TJSObject; PAS_Object: TJSONObject; i: Integer; const SampleObjectData = '{"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable"}'; begin asm var JS_Object = JSON.parse(SampleObjectData); end; WC_Object := TJSJSON.parseObject(SampleObjectData); PAS_Object := TJSONObject.ParseJSONValue(SampleObjectData) as TJSONObject; asm for (var Key in JS_Object) { console.log('JS Key: '+Key+' Value: '+JS_Object[Key]) } end; i := 0; while (i < length(TJSOBject.keys(WC_Object))) do begin console.log('WC Key: '+string(TJSObject.keys(WC_Object)[i])+' Value: '+string(WC_Object[TJSObject.keys(WC_Object)[i]])); i := i + 1; end; i := 0; while (i < PAS_Object.Count) do begin console.log('PAS Key: '+PAS_Object.Pairs[i].JSONString.Value+' Value: '+PAS_Object.Pairs[i].JSONValue.Value); i := i + 1; end; end; console.log output: JS Key: apple Value: fruit JS Key: banana Value: fruit JS Key: orange Value: fruit JS Key: carrot Value: vegetable JS Key: potato Value: vegetable WC Key: apple Value: fruit WC Key: banana Value: fruit WC Key: orange Value: fruit WC Key: carrot Value: vegetable WC Key: potato Value: vegetable PAS Key: apple Value: fruit PAS Key: banana Value: fruit PAS Key: orange Value: fruit PAS Key: carrot Value: vegetable PAS Key: potato Value: vegetable
22 : Traverse Entire JSON Object.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Object: TJSObject; PAS_Object: TJSONObject; const SampleObjectData = '[[[123,{"key":true,"another key":"abc","more keys":[1,2,false]},null]]]'; function WC_Print(Element:TJSObject; Indentation:String; Key:String):String; var str: String; typ: String; begin str := ''; typ := ''; if (Element = nil) then begin str := 'Null (Null)'; typ := 'Null'; end else if (Element.ToString = 'true') then begin str := 'True (Boolean)'; typ := 'Boolean'; end else if (Element.ToString = 'false') then begin str := 'False (Boolean)'; typ := 'Boolean'; end else if (TJSJSON.stringify(Element).startsWith('"')) then begin str := Element.toString+' (String)'; typ := 'String'; end else if (TJSJSON.stringify(Element).startsWith('{')) then begin str := 'Object {'; typ := 'Object'; end else if (TJSJSON.stringify(Element).startsWith('[')) then begin str := 'Array ['; typ := 'Object'; end else begin str := Element.ToString+' (Number)'; typ := 'Number'; end; if (Key = '') then console.log(Indentation+' '+str) else console.log(Indentation+' '+Key+': '+str); Result := typ; end; procedure WC_Output(JSONObject:TJSObject; Indentation:String); var i: integer; typ: string; const indent = '____'; begin if (TJSJSON.stringify(JSONObject).startsWith('[')) then begin if (Indentation = '') then begin console.log('WC ['); Indentation := indent; end; for i := 0 to (length(TJSObject.keys(JSONObject))-1) do begin typ := WC_Print(TJSObject(JSONObject[String(TJSObject.keys(JSONObject)[i])]), Indentation, IntToStr(i)); if (typ = 'Object') then begin WC_Output(TJSObject(JSONObject[String(TJSObject.keys(JSONObject)[i])]), Indentation+indent); console.log(Indentation+' }'); end else if (typ = 'Array') then begin WC_Output(TJSObject(JSONObject[String(TJSObject.keys(JSONObject)[i])]), Indentation+indent); console.log(Indentation+' ]'); end; end; if (Indentation = indent) then console.log(' ]'); end else if (TJSJSON.stringify(JSONObject).startsWith('{')) then begin if (Indentation = '') then begin console.log('WC {'); Indentation := '____'; end; for i := 0 to (length(TJSObject.keys(JSONObject))-1) do begin typ := WC_Print(TJSObject(JSONObject[String(TJSObject.keys(JSONObject)[i])]), Indentation, string(TJSObject.keys(JSONObject)[i])); if (typ = 'Object') then begin WC_Output(TJSObject(JSONObject[String(TJSObject.keys(JSONObject)[i])]), Indentation+indent); console.log(Indentation+' }'); end else if (typ = 'Array') then begin WC_Output(TJSObject(JSONObject[String(TJSObject.keys(JSONObject)[i])]), Indentation+indent); console.log(Indentation+' ]'); end; end; if (Indentation = indent) then console.log('}'); end else begin WC_Print(JSONObject,Indentation,'') end; end; function PAS_Print(Element:TJSONValue; Indentation:String; Key:String):String; var str: String; typ: String; begin str := ''; typ := ''; if (Element.ClassName = 'TJSONNull') then begin str := 'Null (Null)'; typ := 'Null'; end else if (Element.ClassName = 'TJSONTrue') then begin str := 'True (Boolean)'; typ := 'Boolean'; end else if (Element.ClassName = 'TJSONFalse') then begin str := 'False (Boolean)'; typ := 'Boolean'; end else if (Element.ClassName = 'TJSONString') then begin str := Element.toString+' (String)'; typ := 'String'; end else if (Element.ClassName = 'TJSONNumber') then begin str := Element.toString+' (Number)'; typ := 'String'; end else if (Element.ClassName = 'TJSONObject') then begin str := 'Object {'; typ := 'Object'; end else if (Element.ClassName = 'TJSONArray') then begin str := 'Array ['; typ := 'Object'; end; if (Key = '') then console.log(Indentation+' '+str) else console.log(Indentation+' '+Key+': '+str); Result := typ; end; procedure PAS_Output(JSONObject:TJSONObject; Indentation:String); var i: integer; typ: string; const indent = '____'; begin if (JSONObject.ClassName = 'TJSONArray') then begin if (Indentation = '') then begin console.log('PAS ['); Indentation := indent; end; for i := 0 to ((JSONObject as TJSONArray).Count - 1) do begin typ := PAS_Print((JSONObject as TJSONArray)[i] as TJSONValue, Indentation, IntToStr(i)); if (typ = 'Object') then begin PAS_Output((JSONObject as TJSONArray)[i] as TJSONObject, Indentation+indent); console.log(Indentation+' }'); end else if (typ = 'Array') then begin PAS_Output((JSONObject as TJSONArray)[i] as TJSONArray, Indentation+indent); console.log(Indentation+' ]'); end; end; if (Indentation = indent) then console.log(']'); end else if (JSONObject.ClassName = 'TJSONObject') then begin if (Indentation = '') then begin console.log('PAS {'); Indentation := '____'; end; for i := 0 to (JSONObject.Count - 1) do begin typ := PAS_Print(JSONObject.Items[i], Indentation, JSONObject.Pairs[i].JSONString.Value); if (typ = 'Object') then begin PAS_Output(JSONObject.Items[i] as TJSONObject, Indentation+indent); console.log(Indentation+' }'); end else if (typ = 'Array') then begin PAS_Output(JSONObject.Items[i] as TJSONArray, Indentation+indent); console.log(Indentation+' ]'); end; end; if (Indentation = indent) then console.log('}'); end else begin PAS_Print(JSONObject,Indentation,'') end; end; begin asm var JS_Object = JSON.parse(SampleObjectData); end; WC_Object := TJSJSON.parseObject(SampleObjectData); PAS_Object := TJSONObject.ParseJSONValue(SampleObjectData) as TJSONObject; asm function JS_Print(Element, Indentation, Key) { var str = ''; var typ = ''; if (Element === null) { str = 'Null (Null)'; typ = 'Null'; } else if (Element === true) { str = 'True (Boolean)'; typ = 'Boolean'; } else if (Element === false) { str = 'False (Boolean)'; typ = 'Boolean'; } else if (Array.isArray(Element)) { str = 'Array ['; typ = 'Array'; } else if (typeof Element === 'string') { str = '"'+Element+'" (String)'; typ = 'String'; } else if (typeof Element === 'number') { str = Element+' (Number)'; typ = 'Number'; } else if (typeof Element === 'object') { str = 'Object {'; typ = 'Object'; } if (Key === undefined) { console.log(Indentation+' '+str); } else { console.log(Indentation+' '+Key+': '+str); } return(typ) } function JS_Output(JSONObject, Indentation) { const indent = '____'; if (Array.isArray(JSONObject)) { if (Indentation == '') { console.log("JS ["); Indentation = indent; } for (var i = 0; i < JSONObject.length; i++) { switch (JS_Print(JSONObject[i],Indentation,i)) { case 'Object': JS_Output(JSONObject[i],Indentation+indent); console.log(Indentation+" }"); break; case 'Array': JS_Output(JSONObject[i],Indentation+indent); console.log(Indentation+" ]"); break; } } if (Indentation == indent) { console.log("]") } } else if (typeof JSONObject === 'object') { if (Indentation == '') { console.log("JS {"); Indentation = '____'; } for (var key in JSONObject) { switch (JS_Print(JSONObject[key],Indentation,key)) { case 'Object': JS_Output(JSONObject[key],Indentation+indent); console.log(Indentation+" }"); break; case 'Array': JS_Output(JSONObject[key],Indentation+indent); console.log(Indentation+" ]"); break; } } if (Indentation == indent) { console.log("}") } } else { JS_Print(JSONObject,Indentation); } } end; asm JS_Output(JS_Object,''); end; WC_Output(WC_Object,''); PAS_Output(PAS_Object,''); end; console.log output (just JS, WC and PAS are the same): JS [ ____ 0: Array [ ________ 0: Array [ ____________ 0: 123 (Number) ____________ 1: Object { ________________ key: True (Boolean) ________________ another key: "abc" (String) ________________ more keys: Array [ ____________________ 0: 1 (Number) ____________________ 1: 2 (Number) ____________________ 2: False (Boolean) ________________ ] ____________ } ____________ 2: Null (Null) ________ ] ____ ] ]
23 : Compare JSON.
procedure TForm1.WebButton1Click(Sender: TObject);
var
WC_Object1: TJSObject;
WC_Object2: TJSObject;
PAS_Object1: TJSONObject;
PAS_Object2: TJSONObject;
const
SampleObjectData1 = '[[[123,{"key":true,"another key":"abc","more keys":[1,3,false]},null,[{"a":"1","b":2,"c":"3"}]]]]';
SampleObjectData2 = '[[[123,{"another key":"abc","key":true,"more keys":[1,3,false]},null,[{"c":"3","b":2,"a":"1"}]]]]';
function WC_Compare(Element1: TJSObject; Element2:TJSObJect):Boolean;
var
test: Boolean;
i: integer;
begin
test := true;
if ((Element1 = nil) and (Element2 = nil)) then test := true
else if (Element1 = nil) or (Element2 = nil) then test := false
else if ((Element1.toString = 'true') and (Element2.toString = 'true')) then test := true
else if ((Element1.toString = 'false') and (Element2.toString = 'false')) then test := true
else if (TJSJSON.stringify(Element1).startsWith('[')) and (TJSJSON.stringify(Element2).startsWith('[')) and (length(TJSObject.keys(Element1)) = length(TJSObject.keys(Element2))) then
begin
for i := 0 to (length(TJSObject.keys(Element1))-1) do
begin
if not(WC_Compare(TJSObject(Element1[String(TJSObject.keys(Element1)[i])]),TJSObject(Element2[String(TJSObject.keys(ELement2)[i])]))) then
begin
test := false;
end;
end;
end
else if (TJSJSON.stringify(Element1).startsWith('{')) and (TJSJSON.stringify(Element2).startsWith('{')) and (length(TJSObject.keys(Element1)) = length(TJSObject.keys(Element2))) then
begin
for i := 0 to (length(TJSObject.keys(Element1))-1) do
begin
if not(WC_Compare(TJSObject(Element1[String(TJSObject.keys(Element1)[i])]),TJSObject(Element2[String(TJSObject.keys(Element1)[i])]))) then
begin
test := false;
end;
end;
end
else if (TJSJSON.stringify(Element1).startsWith('"')) and (TJSJSON.stringify(Element2).startsWith('"')) and (Element1.toString = Element2.toString) then test := true
else if (Element1.toString = Element2.toString) then test := true
else
begin
test := false;
end;
// if not(test) then console.log(TJSJSON.stringify(Element1)+' <> '+TJSJSON.stringify(Element2));
Result := test;
end;
function PAS_Compare(Element1: TJSONValue; Element2:TJSONValue):Boolean;
var
test: Boolean;
i: integer;
begin
test := true;
if ((Element1.ClassName = 'TJSONNull') and (Element2.Classname = 'TJSONNull')) then test := true
else if ((Element1.ClassName = 'TJSONNull') or (Element2.Classname = 'TJSONNull')) then test := false
else if ((Element1.ClassName = 'TJSONTrue') and (Element2.Classname = 'TJSONTrue')) then test := true
else if ((Element1.ClassName = 'TJSONFalse') and (Element2.Classname = 'TJSONFalse')) then test := true
else if ((Element1.ClassName = 'TJSONString') and (Element2.Classname = 'TJSONString')) and (Element1.ToString = Element2.ToString) then test := true
else if ((Element1.ClassName = 'TJSONNumber') and (Element2.Classname = 'TJSONNumber')) and (Element1.ToString = Element2.ToString) then test := true
else if ((Element1.ClassName = 'TJSONArray') and (Element2.Classname = 'TJSONArray')) and ((Element1 as TJSONArray).Count = (Element2 as TJSONArray).Count) then
begin
for i := 0 to ((Element1 as TJSONArray).Count - 1) do
begin
if not(PAS_Compare((Element1 as TJSONArray).Items[i], ((Element2 as TJSONArray).Items[i]))) then
begin
test := false;
end;
end;
end
else if ((Element1.ClassName = 'TJSONObject') and (Element2.Classname = 'TJSONObject')) and ((Element1 as TJSONOBject).Count = (Element2 as TJSONObject).Count) then
begin
for i := 0 to ((Element1 as TJSONObject).Count - 1) do
begin
if not(PAS_Compare((Element1 as TJSONObject).Pairs[i].JSONValue,(Element2 as TJSONObject).Get((Element1 as TJSONObject).Pairs[i].JSONString.value).JSONValue)) then
begin
test := false;
end;
end;
end
else
begin
if Element1.ToString <> Element2.toString
then test := false
else test := true;
end;
// if not(test) then console.log(Element1.ToString+' <> '+Element2.ToString);
Result := test;
end;
begin
asm var JS_Object1 = JSON.parse(SampleObjectData1); end;
asm var JS_Object2 = JSON.parse(SampleObjectData2); end;
WC_Object1 := TJSJSON.parseObject(SampleObjectData1);
WC_Object2 := TJSJSON.parseObject(SampleObjectData2);
PAS_Object1 := TJSONObject.ParseJSONValue(SampleObjectData1) as TJSONObject;
PAS_Object2 := TJSONObject.ParseJSONValue(SampleObjectData2) as TJSONObject;
asm
function JS_Compare(Element1, Element2) {
var test = true;
if ((Element1 === null) && (Element2 === null)) {test = true}
else if ((Element1 === null) || (Element2 === null)) {test = false}
else if ((Element1 === true) && (Element2 === true)) {test = true}
else if ((Element1 === false) && (Element2 === false)) {test = true}
else if ((Array.isArray(Element1)) && (Array.isArray(Element2)) && (Element1.length == Element2.length)) {
for (var i = 0; i < Element1.length; i++) {
if (!JS_Compare(Element1[i],Element2[i])) {
test = false;
}
}
}
else if ((typeof Element1 === 'string') && (typeof Element2 === 'string') && (Element1 === Element2)) {test = true}
else if ((typeof Element1 === 'number') && (typeof Element2 === 'number') && (Element1 === Element2)) {test = true}
else if ((typeof Element1 === 'object') && (typeof Element2 === 'object') && (Object.keys(Element1).length == Object.keys(Element2).length)) {
for (var key in Element1) {
if (!JS_Compare(Element1[key],Element2[key])) {
test = false;
}
}
}
else {
test = false;
}
// if (!test) {console.log(JSON.stringify(Element1)+' <> '+JSON.stringify(Element2))};
return(test);
}
end;
asm console.log('JS '+JS_Compare(JS_Object1, JS_Object2)); end;
console.log('WC '+BoolToStr(WC_Compare(WC_Object1,WC_Object2),true));
console.log('PAS '+BoolToStr(PAS_Compare(PAS_Object1,PAS_Object2),true));
end;
console.log output:
JS true
WC True
PAS True
24 : Sort JSON Object Elements.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Object: TJSObject; PAS_Object: TJSONObject; i: integer; WC_Keys: TStringList; PAS_Keys: TStringList; const SampleObjectData = '{"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable"}'; begin asm var JS_Object = JSON.parse(SampleObjectData); end; WC_Object := TJSJSON.parseObject(SampleObjectData); PAS_Object := TJSONObject.ParseJSONValue(SampleObjectData) as TJSONObject; asm var keys = Object.keys(JS_Object).sort(); for (var i = 0; i < keys.length; i++) { console.log('JS Key: '+keys[i]+' Value: '+JS_Object[keys[i]]) } end; // There are other ways to sort but I didn't have much luck here. // For example, System.Generics.Collections would hang compiler for some reason WC_Keys := TStringList.Create; for i := 0 to length(TJSObject.keys(WC_Object))-1 do WC_Keys.Add(TJSObject.keys(WC_Object)[i]); WC_Keys.sort; for i := 0 to WC_Keys.Count -1 do console.log('WC Key: '+WC_Keys[i]+' Value: '+string(WC_Object[WC_Keys[i]])); // Same approach used here for same reason PAS_Keys := TStringList.Create; for i := 0 to PAS_Object.Count-1 do PAS_Keys.Add(PAS_Object.Pairs[i].JSONString.Value); PAS_Keys.sort; for i := 0 to PAS_Keys.Count -1 do console.log('PAS Key: '+PAS_Keys[i]+' Value: '+PAS_Object.Get(PAS_Keys[i]).JSONValue.ToString); end; console.log output: JS Key: apple Value: fruit JS Key: banana Value: fruit JS Key: carrot Value: vegetable JS Key: orange Value: fruit JS Key: potato Value: vegetable WC Key: apple Value: fruit WC Key: banana Value: fruit WC Key: carrot Value: vegetable WC Key: orange Value: fruit WC Key: potato Value: vegetable PAS Key: apple Value: "fruit" PAS Key: banana Value: "fruit" PAS Key: carrot Value: "vegetable" PAS Key: orange Value: "fruit" PAS Key: potato Value: "vegetable"
25 : Move from JS to WC or PAS.
procedure TForm1.WebButton1Click(Sender: TObject); var JS_Object: JSValue; const SampleObjectData = '{"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable"}'; begin asm JS_Object = JSON.parse(SampleObjectData); end; // WC: Delphi code to access JavaScript JSON Object // Wrapping it in TJSObject() makes it equivalent to a WC TJSObject console.log('WC: '+TJSJSON.stringify(JS_Object)); console.log('WC: '+IntToStr(length(TJSObject.keys(TJSObject(JS_Object))))); // PAS: Converting to a TJSONObject via Strings works console.log('PAS: '+(TJSONObject.parseJSONValue(TJSJSON.stringify(JS_Object)) as TJSONObject).ToString); console.log('PAS: '+IntToStr((TJSONObject.parseJSONValue(TJSJSON.stringify(JS_Object)) as TJSONObject).Count)); end; console.log output: WC: {"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable"} WC: 5 PAS: {"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable"} PAS: 5
26 : Move from WC to JS or PAS.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Object: TJSObject; const SampleObjectData = '{"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable"}'; begin WC_Object := TJSJSON.parseObject(SampleObjectData); // JS: Works as if it were natively defined in JS asm console.log('JS: '+JSON.stringify(WC_Object)); console.log('JS: '+Object.keys(WC_Object).length); end; // PAS: Converting to a TJSONObject via Strings works as before console.log('PAS: '+(TJSONObject.parseJSONValue(TJSJSON.stringify(WC_Object)) as TJSONObject).ToString); console.log('PAS: '+IntToStr((TJSONObject.parseJSONValue(TJSJSON.stringify(WC_Object)) as TJSONObject).Count)); end; console.log output: JS: {"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable"} JS: 5 PAS: {"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable"} PAS: 5
27 : Move from PAS to JS or WC.
{"fjv":{"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable"},"fjo$1":{"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable"},"FMembers":{"FList":{"FList":[],"FCount":0,"FCapacity":0}}}
procedure TForm1.WebButton1Click(Sender: TObject); var PAS_Object: TJSONObject; const SampleObjectData = '{"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable"}'; begin PAS_Object := TJSONObject.parseJSONValue(SampleObjectData) as TJSONObject; // JS asm console.log('JS: '+JSON.stringify(PAS_Object['fjv'])); console.log('JS: '+Object.keys(PAS_Object['fjv']).length); end; // WC console.log('WC: '+TJSJSON.stringify(PAS_Object.JSObject)); console.log('WC: '+IntToStr(length(TJSObject.keys(PAS_Object.JSObject)))); end; console.log output: JS: {"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable"} JS: 5 WC: {"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable"} WC: 5
28 : FireDAC to JSON (Simple).
procedure TMyService.GetJSONData(ParametersOfSomeKind: String):TStream; var clientDB: TFDConnection; qry: TFDQuery; bm: TFDBatchMove; bw: TFDBatchMoveJSONWriter; br: TFDBatchMoveDataSetReader; begin Result := TMemoryStream.Create; // Some kind of database connection clientDB := TFDConnection.Create(nil); clientDB.ConnectionDefName := SomeDatabaseConnection; clientDB.Connected := True; // Some kind of query qry.Connection := clientDB; qry.SQL.Text := 'select * from DATAMARK.LUTS;'; qry.Open; // Convert the query to simplified JSON bm := TFDBatchMove.Create(nil); bw := TFDBatchMoveJSONWriter.Create(nil); br := TFDBatchMoveDataSetReader.Create(nil); try br.Dataset := qry; bw.Stream := Result; bm.Reader := br; bm.Writer := bw; bm.Execute; finally br.Free; bw.Free; bm.Free; end; // Cleanup afterwards qry.Free; clientDB.Connected := False; clientDB.Free; end;
[{"ID":1,"LOOKUP":0,"SORTORDER":0,"RESPONSE":"Administration","MODIFIER":"ASIMARD","MODIFIED":"2021-11-17T19:42:34","GROUPTYPE":16},{"ID":2,"LOOKUP":1,"SORTORDER":1,"RESPONSE":"Labour","MODIFIER":"ASIMARD","MODIFIED":"2021-11-17T19:47:52","GROUPTYPE":16},{"ID":3,"LOOKUP":2,"SORTORDER":2,"RESPONSE":"IT","MODIFIER":"ASIMARD","MODIFIED":"2021-11-17T19:42:56","GROUPTYPE":16}]
[
{
"ID": 1,
"LOOKUP": 0,
"SORTORDER": 0,
"RESPONSE": "Administration",
"MODIFIER": "ASIMARD",
"MODIFIED": "2021-11-17T19:42:34",
"GROUPTYPE": 16
},
{
"ID": 2,
"LOOKUP": 1,
"SORTORDER": 1,
"RESPONSE": "Labour",
"MODIFIER": "ASIMARD",
"MODIFIED": "2021-11-17T19:47:52",
"GROUPTYPE": 16
},
{
"ID": 3,
"LOOKUP": 2,
"SORTORDER": 2,
"RESPONSE": "IT",
"MODIFIER": "ASIMARD",
"MODIFIED": "2021-11-17T19:42:56",
"GROUPTYPE": 16
}
]
29 : FireDAC to JSON (Complex).
procedure TMyService.GetJSONData(ParametersOfSomeKind: String):TStream; var clientDB: TFDConnection; qry: TFDQuery; begin Result := TMemoryStream.Create; // Some kind of database connection clientDB := TFDConnection.Create(nil); clientDB.ConnectionDefName := SomeDatabaseConnection; clientDB.Connected := True; // Some kind of query qry.Connection := clientDB; qry.SQL.Text := 'select * from sample;'; qry.Open; // Convert the query to FireDAC's JSON qry.SaveToStream(Result, sfJSON); // Cleanup afterwards qry.Free; clientDB.Connected := False; clientDB.Free; end;
{"FDBS":{"Version":15,"Manager":{"UpdatesRegistry":true,"TableList":[{"class":"Table","Name":"DATAMARK.LUTS","SourceName":"DATAMARK.LUTS","SourceID":1,"TabID":0,"EnforceConstraints":false,"MinimumCapacity":50,"ColumnList":[{"class":"Column","Name":"ID","SourceName":"ID","SourceID":1,"DataType":"Int64","Precision":19,"Searchable":true,"Base":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"ID","SourcePrecision":19},{"class":"Column","Name":"LOOKUP","SourceName":"LOOKUP","SourceID":2,"DataType":"Int32","Precision":10,"Searchable":true,"Base":true,"OInUpdate":true,"OInWhere":true,"OInKey":true,"OriginColName":"LOOKUP","SourcePrecision":10},{"class":"Column","Name":"SORTORDER","SourceName":"SORTORDER","SourceID":3,"DataType":"Int32","Precision":10,"Searchable":true,"Base":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"SORTORDER","SourcePrecision":10},{"class":"Column","Name":"RESPONSE","SourceName":"RESPONSE","SourceID":4,"DataType":"AnsiString","Size":200,"Searchable":true,"Base":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"RESPONSE","SourcePrecision":200,"SourceSize":200},{"class":"Column","Name":"DESCRIPTION","SourceName":"DESCRIPTION","SourceID":5,"DataType":"AnsiString","Size":250,"Searchable":true,"AllowNull":true,"Base":true,"OAllowNull":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"DESCRIPTION","SourcePrecision":250,"SourceSize":250},{"class":"Column","Name":"MODIFIER","SourceName":"MODIFIER","SourceID":6,"DataType":"AnsiString","Size":32,"Searchable":true,"FixedLen":true,"Base":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"MODIFIER","SourcePrecision":32,"SourceSize":32},{"class":"Column","Name":"MODIFIED","SourceName":"MODIFIED","SourceID":7,"DataType":"DateTimeStamp","Searchable":true,"AllowNull":true,"Base":true,"OAllowNull":true,"OInUpdate":true,"OInWhere":true,"OriginColName":"MODIFIED","SourcePrecision":26},{"class":"Column","Name":"GROUPTYPE","SourceName":"GROUPTYPE","SourceID":8,"DataType":"Int32","Precision":10,"Searchable":true,"Base":true,"OInUpdate":true,"OInWhere":true,"OInKey":true,"OriginColName":"GROUPTYPE","SourcePrecision":10}],"ConstraintList":[],"ViewList":[],"RowList":[{"RowID":0,"Original":{"ID":1,"LOOKUP":0,"SORTORDER":0,"RESPONSE":"Administration","MODIFIER":"ASIMARD","MODIFIED":"2021-11-17T19:42:34","GROUPTYPE":16}},{"RowID":1,"Original":{"ID":2,"LOOKUP":1,"SORTORDER":1,"RESPONSE":"Labour","MODIFIER":"ASIMARD","MODIFIED":"2021-11-17T19:47:52","GROUPTYPE":16}},{"RowID":2,"Original":{"ID":3,"LOOKUP":2,"SORTORDER":2,"RESPONSE":"IT","MODIFIER":"ASIMARD","MODIFIED":"2021-11-17T19:42:56","GROUPTYPE":16}}]}],"RelationList":[],"UpdatesJournal":{"Changes":[]}}}}
{
"FDBS": {
"Version": 15,
"Manager": {
"UpdatesRegistry": true,
"TableList": [
{
"class": "Table",
"Name": "DATAMARK.LUTS",
"SourceName": "DATAMARK.LUTS",
"SourceID": 1,
"TabID": 0,
"EnforceConstraints": false,
"MinimumCapacity": 50,
"ColumnList": [
{
"class": "Column",
"Name": "ID",
"SourceName": "ID",
"SourceID": 1,
"DataType": "Int64",
"Precision": 19,
"Searchable": true,
"Base": true,
"OInUpdate": true,
"OInWhere": true,
"OriginColName": "ID",
"SourcePrecision": 19
},
{
"class": "Column",
"Name": "LOOKUP",
"SourceName": "LOOKUP",
"SourceID": 2,
"DataType": "Int32",
"Precision": 10,
"Searchable": true,
"Base": true,
"OInUpdate": true,
"OInWhere": true,
"OInKey": true,
"OriginColName": "LOOKUP",
"SourcePrecision": 10
},
{
"class": "Column",
"Name": "SORTORDER",
"SourceName": "SORTORDER",
"SourceID": 3,
"DataType": "Int32",
"Precision": 10,
"Searchable": true,
"Base": true,
"OInUpdate": true,
"OInWhere": true,
"OriginColName": "SORTORDER",
"SourcePrecision": 10
},
{
"class": "Column",
"Name": "RESPONSE",
"SourceName": "RESPONSE",
"SourceID": 4,
"DataType": "AnsiString",
"Size": 200,
"Searchable": true,
"Base": true,
"OInUpdate": true,
"OInWhere": true,
"OriginColName": "RESPONSE",
"SourcePrecision": 200,
"SourceSize": 200
},
{
"class": "Column",
"Name": "DESCRIPTION",
"SourceName": "DESCRIPTION",
"SourceID": 5,
"DataType": "AnsiString",
"Size": 250,
"Searchable": true,
"AllowNull": true,
"Base": true,
"OAllowNull": true,
"OInUpdate": true,
"OInWhere": true,
"OriginColName": "DESCRIPTION",
"SourcePrecision": 250,
"SourceSize": 250
},
{
"class": "Column",
"Name": "MODIFIER",
"SourceName": "MODIFIER",
"SourceID": 6,
"DataType": "AnsiString",
"Size": 32,
"Searchable": true,
"FixedLen": true,
"Base": true,
"OInUpdate": true,
"OInWhere": true,
"OriginColName": "MODIFIER",
"SourcePrecision": 32,
"SourceSize": 32
},
{
"class": "Column",
"Name": "MODIFIED",
"SourceName": "MODIFIED",
"SourceID": 7,
"DataType": "DateTimeStamp",
"Searchable": true,
"AllowNull": true,
"Base": true,
"OAllowNull": true,
"OInUpdate": true,
"OInWhere": true,
"OriginColName": "MODIFIED",
"SourcePrecision": 26
},
{
"class": "Column",
"Name": "GROUPTYPE",
"SourceName": "GROUPTYPE",
"SourceID": 8,
"DataType": "Int32",
"Precision": 10,
"Searchable": true,
"Base": true,
"OInUpdate": true,
"OInWhere": true,
"OInKey": true,
"OriginColName": "GROUPTYPE",
"SourcePrecision": 10
}
],
"ConstraintList": [],
"ViewList": [],
"RowList": [
{
"RowID": 0,
"Original": {
"ID": 1,
"LOOKUP": 0,
"SORTORDER": 0,
"RESPONSE": "Administration",
"MODIFIER": "ASIMARD",
"MODIFIED": "2021-11-17T19:42:34",
"GROUPTYPE": 16
}
},
{
"RowID": 1,
"Original": {
"ID": 2,
"LOOKUP": 1,
"SORTORDER": 1,
"RESPONSE": "Labour",
"MODIFIER": "ASIMARD",
"MODIFIED": "2021-11-17T19:47:52",
"GROUPTYPE": 16
}
},
{
"RowID": 2,
"Original": {
"ID": 3,
"LOOKUP": 2,
"SORTORDER": 2,
"RESPONSE": "IT",
"MODIFIER": "ASIMARD",
"MODIFIED": "2021-11-17T19:42:56",
"GROUPTYPE": 16
}
}
]
}
],
"RelationList": [],
"UpdatesJournal": {
"Changes": []
}
}
}
}
30 : JSON to TDataset (Simple).

procedure TForm1.WebButton1Click(Sender: TObject); var WC_Object: TJSObject; const SampleObjectData = '[{"ID":1,"LOOKUP":0,"SORTORDER":0,"RESPONSE":"Administration","MODIFIER":"ASIMARD","MODIFIED":"2021-11-17T19:42:34","GROUPTYPE":16},'+ '{"ID":2,"LOOKUP":1,"SORTORDER":1,"RESPONSE":"Labour","MODIFIER":"ASIMARD","MODIFIED":"2021-11-17T19:47:52","GROUPTYPE":16},'+ '{"ID":3,"LOOKUP":2,"SORTORDER":2,"RESPONSE":"IT","MODIFIER":"ASIMARD","MODIFIED":"2021-11-17T19:42:56","GROUPTYPE":16}]'; begin WC_Object := TJSJSON.parseObject(SampleObjectData); XDataWebDataSet1.SetJSONData(WC_Object); XdataWebDataSet1.Open; end;

31 : JSON to TDataset (Complex).
function GetQueryData(Endpoint: String; Dataset: TXDataWebDataSet):Integer; var Conn: TXDataWebConnection; Client: TXDataWebClient; Response: TXDataClientResponse; JFDBS: TJSObject; JManager: TJSObject; JTableList: TJSObject; JColumnList: TJSArray; JRowList: TJSArray; StringFields: Array of TStringField; IntegerFields: Array of TIntegerField; // Likely need a few more datatypes here i: Integer; begin // Value to indicate the request was unsuccessful Result := -1; // Setup connection to XData Server Conn := TXDataWebConnection.Create(nil); Conn.URL := DM1.CarnivalCoreServer; // could also be a parameter Conn.OnRequest := PopulateJWT; // See below Client := TXDataWebClient.Create(nil); Client.Connection := Conn; await(Conn.OpenAsync); // Make the Request // Likely to have another version of function that includes more endpoint parameters try Response := await(Client.RawInvokeAsync(Endpoint, ['FireDAC'])); except on Error: Exception do begin Client.Free; Conn.Free; console.log('...something is amiss...'); exit; end; end; // Process the FireDAC-specific JSON that was returned JFDBS := TJSObject(TJSObject(TJSJson.Parse(string(Response.Result)))['FDBS']); JManager := TJSObject(JFDBS['Manager']); JTableList := TJSObject(TJSArray(JManager['TableList'])[0]); JColumnList := TJSArray(JTableList['ColumnList']); JRowList := TJSArray(JTableList['RowList']); // 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 i := 0 to JRowList.Length - 1 do JRowList.Elements[i] := TJSObject(JRowList.Elements[i])['Original']; // We're assuming Dataset parameter is newly created and empty. // First, add all the fields from JSON // NOTE: Very likely more datatypes need to be added here for i := 0 to JColumnList.Length-1 do begin if (String(TJSObject(JColumnList.Elements[i])['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(Dataset); StringFields[Length(StringFields)-1].FieldName := String(TJSObject(JColumnList.Elements[i])['Name']); StringFields[Length(StringFields)-1].Size := Integer(TJSObject(JColumnList.Elements[i])['Size']); StringFields[Length(StringFields)-1].Dataset := Dataset; end else if (String(TJSObject(JColumnList.Elements[i])['DataType']) = 'Int32') then begin SetLength(IntegerFields, Length(IntegerFields) + 1); IntegerFields[Length(IntegerFields)-1] := TIntegerField.Create(Dataset); IntegerFields[Length(IntegerFields)-1].FieldName := String(TJSObject(JColumnList.Elements[i])['Name']); IntegerFields[Length(IntegerFields)-1].Dataset := Dataset; end else begin console.log('ERROR: Field ['+String(TJSObject(JColumnList.Elements[i])['Name'])+'] has an unexpected datatype ['+String(TJSObject(JColumnList.Elements[i])['DataType'])+']'); end; end; // Add the data and return the dataset as opened Dataset.SetJSONData(JRowList); Dataset.Open; // Just for fun Result := Dataset.RecordCount; // No dataset stuff to free as the dataset was created by the caller and // all the fields created were created with that dataset as the parent Client.Free; Conn.Free; end;
// Process the FireDAC-specific JSON that was returned
JFDBS := TJSObject(TJSObject(TJSJson.Parse(string(Response.Result)))['FDBS']);
JManager := TJSObject(JFDBS['Manager']);
JTableList := TJSObject(TJSArray(JManager['TableList'])[0]);
JColumnList := TJSArray(JTableList['ColumnList']);
JRowList := TJSArray(JTableList['RowList']);
// 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 i := 0 to JRowList.Length - 1 do
JRowList.Elements[i] := TJSObject(JRowList.Elements[i])['Original'];
JFDBS := TJSObject(TJSObject(TJSJson.Parse(string(Response.Result)))['FDBS']);
JManager := TJSObject(JFDBS['Manager']);
JTableList := TJSObject(TJSArray(JManager['TableList'])[0]);
{
"class": "Table",
"Name": "DATAMARK.LUTS",
"SourceName": "DATAMARK.LUTS",
"SourceID": 1,
"TabID": 0,
"EnforceConstraints": false,
"MinimumCapacity": 50,
"ColumnList": [
{
"class": "Column",
"Name": "ID",
"SourceName": "ID",
"SourceID": 1,
"DataType": "Int64",
"Precision": 19,
"Searchable": true,
"Base": true,
"OInUpdate": true,
"OInWhere": true,
"OriginColName": "ID",
"SourcePrecision": 19
},
{
"class": "Column",
"Name": "LOOKUP",
"SourceName": "LOOKUP",
"SourceID": 2,
"DataType": "Int32",
"Precision": 10,
"Searchable": true,
"Base": true,
"OInUpdate": true,
"OInWhere": true,
"OInKey": true,
"OriginColName": "LOOKUP",
"SourcePrecision": 10
},
{
"class": "Column",
"Name": "SORTORDER",
"SourceName": "SORTORDER",
"SourceID": 3,
"DataType": "Int32",
"Precision": 10,
"Searchable": true,
"Base": true,
"OInUpdate": true,
"OInWhere": true,
"OriginColName": "SORTORDER",
"SourcePrecision": 10
},
{
"class": "Column",
"Name": "RESPONSE",
"SourceName": "RESPONSE",
"SourceID": 4,
"DataType": "AnsiString",
"Size": 200,
"Searchable": true,
"Base": true,
"OInUpdate": true,
"OInWhere": true,
"OriginColName": "RESPONSE",
"SourcePrecision": 200,
"SourceSize": 200
},
{
"class": "Column",
"Name": "DESCRIPTION",
"SourceName": "DESCRIPTION",
"SourceID": 5,
"DataType": "AnsiString",
"Size": 250,
"Searchable": true,
"AllowNull": true,
"Base": true,
"OAllowNull": true,
"OInUpdate": true,
"OInWhere": true,
"OriginColName": "DESCRIPTION",
"SourcePrecision": 250,
"SourceSize": 250
},
{
"class": "Column",
"Name": "MODIFIER",
"SourceName": "MODIFIER",
"SourceID": 6,
"DataType": "AnsiString",
"Size": 32,
"Searchable": true,
"FixedLen": true,
"Base": true,
"OInUpdate": true,
"OInWhere": true,
"OriginColName": "MODIFIER",
"SourcePrecision": 32,
"SourceSize": 32
},
{
"class": "Column",
"Name": "MODIFIED",
"SourceName": "MODIFIED",
"SourceID": 7,
"DataType": "DateTimeStamp",
"Searchable": true,
"AllowNull": true,
"Base": true,
"OAllowNull": true,
"OInUpdate": true,
"OInWhere": true,
"OriginColName": "MODIFIED",
"SourcePrecision": 26
},
{
"class": "Column",
"Name": "GROUPTYPE",
"SourceName": "GROUPTYPE",
"SourceID": 8,
"DataType": "Int32",
"Precision": 10,
"Searchable": true,
"Base": true,
"OInUpdate": true,
"OInWhere": true,
"OInKey": true,
"OriginColName": "GROUPTYPE",
"SourcePrecision": 10
}
],
"ConstraintList": [],
"ViewList": [],
"RowList": [
{
"RowID": 0,
"Original": {
"ID": 1,
"LOOKUP": 0,
"SORTORDER": 0,
"RESPONSE": "Administration",
"MODIFIER": "ASIMARD",
"MODIFIED": "2021-11-17T19:42:34",
"GROUPTYPE": 16
}
},
{
"RowID": 1,
"Original": {
"ID": 2,
"LOOKUP": 1,
"SORTORDER": 1,
"RESPONSE": "Labour",
"MODIFIER": "ASIMARD",
"MODIFIED": "2021-11-17T19:47:52",
"GROUPTYPE": 16
}
},
{
"RowID": 2,
"Original": {
"ID": 3,
"LOOKUP": 2,
"SORTORDER": 2,
"RESPONSE": "IT",
"MODIFIER": "ASIMARD",
"MODIFIED": "2021-11-17T19:42:56",
"GROUPTYPE": 16
}
}
]
}
JColumnList := TJSArray(JTableList['ColumnList']);
JRowList := TJSArray(JTableList['RowList']);
[
{
"RowID": 0,
"Original": {
"ID": 1,
"LOOKUP": 0,
"SORTORDER": 0,
"RESPONSE": "Administration",
"MODIFIER": "ASIMARD",
"MODIFIED": "2021-11-17T19:42:34",
"GROUPTYPE": 16
}
},
{
"RowID": 1,
"Original": {
"ID": 2,
"LOOKUP": 1,
"SORTORDER": 1,
"RESPONSE": "Labour",
"MODIFIER": "ASIMARD",
"MODIFIED": "2021-11-17T19:47:52",
"GROUPTYPE": 16
}
},
{
"RowID": 2,
"Original": {
"ID": 3,
"LOOKUP": 2,
"SORTORDER": 2,
"RESPONSE": "IT",
"MODIFIER": "ASIMARD",
"MODIFIED": "2021-11-17T19:42:56",
"GROUPTYPE": 16
}
}
]
// 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 i := 0 to JRowList.Length - 1 do JRowList.Elements[i] := TJSObject(JRowList.Elements[i])['Original'];
[
{
"ID": 1,
"LOOKUP": 0,
"SORTORDER": 0,
"RESPONSE": "Administration",
"MODIFIER": "ASIMARD",
"MODIFIED": "2021-11-17T19:42:34",
"GROUPTYPE": 16
},
{
"ID": 2,
"LOOKUP": 1,
"SORTORDER": 1,
"RESPONSE": "Labour",
"MODIFIER": "ASIMARD",
"MODIFIED": "2021-11-17T19:47:52",
"GROUPTYPE": 16
},
{
"ID": 3,
"LOOKUP": 2,
"SORTORDER": 2,
"RESPONSE": "IT",
"MODIFIER": "ASIMARD",
"MODIFIED": "2021-11-17T19:42:56",
"GROUPTYPE": 16
}
]
// We're assuming Dataset parameter is newly created and empty.
// First, add all the fields from JSON
// NOTE: Very likely more datatypes need to be added here
for i := 0 to JColumnList.Length-1 do
begin
if (String(TJSObject(JColumnList.Elements[i])['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(Dataset);
StringFields[Length(StringFields)-1].FieldName := String(TJSObject(JColumnList.Elements[i])['Name']);
StringFields[Length(StringFields)-1].Size := Integer(TJSObject(JColumnList.Elements[i])['Size']);
StringFields[Length(StringFields)-1].Dataset := Dataset;
end
else if (String(TJSObject(JColumnList.Elements[i])['DataType']) = 'Int32') then
begin
SetLength(IntegerFields, Length(IntegerFields) + 1);
IntegerFields[Length(IntegerFields)-1] := TIntegerField.Create(Dataset);
IntegerFields[Length(IntegerFields)-1].FieldName := String(TJSObject(JColumnList.Elements[i])['Name']);
IntegerFields[Length(IntegerFields)-1].Dataset := Dataset;
end
else
begin
console.log('ERROR: Field ['+String(TJSObject(JColumnList.Elements[i])['Name'])+'] has an unexpected datatype ['+String(TJSObject(JColumnList.Elements[i])['DataType'])+']');
end;
end;
Conclusion.
Masiha Zemarai
Bookmarks:

This blog post has received 6 comments.

Great article on JSON.
Just what I needed to start to better understand this subject.
1. Do you know if the example code is going to work on older versions of Delphi. ( I am on Delphi Berlin 10.1.2 Ent/Arch) ?
(I suspect that its not always possible to support lower versions ; as the libraries for delphi etc. are always being updated and improved.)
If that is the case are there any work arounds to get this code to work and explore its possibilities?
2. Any plan eventually , for a full source project download of these snippets. Always useful to have.
Best regards
Kamran

Voirol Jean-No

I will set up a GitHub repository with these examples and also separated-out versions of these examples and will post a link here when that is ready.
Simard Andrew

Simard Andrew

Simard Andrew
All Blog Posts | Next Post | Previous Post
These chapters on JSON handling is a solid reference. Thanks.
Borbor Mehmet Emin