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

Motivation.
- JS: 100% JavaScript code can be used within asm ... end blocks, using JSON in its native environment.
- WC: TMS WEB Core-supplied JSON wrappers help keep code in Delphi while more directly utilizing the underlying JavaScript environment, using TJSObject as a starting point.
- PAS: 100% Delphi that works the same in TMS WEB Core, VCL, and FireMonkey projects, using TJSONObject as a starting point, avoiding JavaScript entirely.
01 : So What is JSON?

- Objects are wrapped in curly brackets {}.
- Objects are unordered lists of comma-separated Key:Value pairs.
- Keys are wrapped in double-quotes, with a colon separating Key and Value.
- Arrays are wrapped in square brackets [].
- Arrays are ordered lists of Objects, Arrays or Values.
- Values can be strings (wrapped in double-quotes).
- Values can be numbers (integer or decimal).
- Values can be boolean or null (only true, false, and null are permitted here).
- Values can be Objects.
- Values can be Arrays.
02 : Defining Objects.
... uses ... JS, // Gets us TJS* types (included by default) WEBlib.JSON, // Gets us TJSON* types (not included by default) ...
procedure TForm1.WebButton1Click(Sender: TObject); var // Environment: JavaScript (JS) // Normally we won't need to define these ahead of time // but just in the asm block directly. // JSValue is kind of like a variant - we don't know what it is JS_Object: JSValue; JS_Array: JSValue; // Environment: TMS WEB Core JavaScript Wrapper (WC) WC_Object: TJSObject; WC_Array: TJSArray; // Environment: Delphi (PAS) PAS_Object: TJSONObject; PAS_Array: TJSONArray; begin // Environment: JavaScript (JS) asm JS_Object = {}; JS_Array = []; console.log('JS Object = '+JSON.stringify(JS_Object)); console.log('JS Array = '+JSON.stringify(JS_Array)); end; // Environment: TMS WEB Core JavaScript Wrapper (WC) WC_Object := TJSObject.new; WC_Array := TJSArray.new; console.log('WC Object = '+TJSJSON.stringify(WC_Object)); console.log('WC Array = '+TJSJSON.stringify(WC_Array)); // Environment: Delphi (PAS) PAS_Object := TJSONObject.Create; PAS_Array := TJSONArray.Create; console.log('PAS Object = '+PAS_Object.ToString); console.log('PAS Array = '+PAS_Array.ToString); end; console.log output: JS Object = {} JS Array = [] WC Object = {} WC Array = [] PAS Object = {} PAS Array = []
- {} is valid JSON
- [] is valid JSON
- {{}} is not valid JSON
- {[]} is not valid JSON
- [[]] is valid JSON
- [{}] is valid JSON
- {"":null} is valid JSON
- {"":[]} is valid JSON
- [[],[]] is valid JSON
- [{},{}] is valid JSON
- [""] is valid JSON
- [true,false,null] is valid JSON
03 : Loading JSON Sample Data.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Array: TJSArray; PAS_Array: TJSONArray; BigSampleData: WideString; begin // Credit: https://gist.github.com/ggordonutech/c04f47fcdad76f8e5e77ee38a9614773 BigSampleData := '['+ '{"id":1,"name":"Banana","description":"A banana is an edible fruit botanically a berry produced by several kinds of large herbaceous flowering plants in the genus Musa. In some countries, bananas used for cooking may be called plantains, distinguishing them from dessert banana","photoUrl":"http://www.pngall.com/wp-content/uploads/2016/04/Banana-PNG.png"},'+ '{"id":2,"name":"Apple","description":"An apple is a sweet, edible fruit produced by an apple tree. Apple trees are cultivated worldwide and are the most widely grown species in the genus Malus.","photoUrl":"https://images.freshop.com/00852201002228/ad2f58915e3267700906f1025ef8917f_medium.png"},'+ '{"id":3,"name":"Peach","description":"The peach is a deciduous tree native to the region of Northwest China between the Tarim Basin and the north slopes of the Kunlun Mountains, where it was first domesticated and cultivated. It bears an edible juicy fruit called a peach or a nectarine.","photoUrl":"http://icons.iconarchive.com/icons/artbees/paradise-fruits/256/Peach-icon.png"},'+ '{"id":4,"name":"Strawberry","description":"The fruit is widely appreciated for its characteristic aroma, bright red color, juicy texture, and sweetness.","photoUrl":"https://static.wixstatic.com/media/2cd43b_7415c9b79d664508b6f62a6953403b75~mv2.png/v1/fill/w_256,h_256,fp_0.50_0.50/2cd43b_7415c9b79d664508b6f62a6953403b75~mv2.png"},'+ '{"id":5,"name":"Tomato","description":"The tomato is the edible, often red, berry of the plant Solanum lycopersicum, commonly known as a tomato plant. The species originated in western South America and Central America.","photoUrl":"https://www.kampexport.com/sites/kampexport.com/files/images/legume/image/tomates_256_1.jpg"},'+ '{"id":6,"name":"Cherry","description":"A cherry is the fruit of many plants of the genus Prunus, and is a fleshy drupe. Commercial cherries are obtained from cultivars of several species, such as the sweet Prunus avium and the sour Prunus cerasus.","photoUrl":"https://cdn.shopify.com/s/files/1/0610/2881/products/cherries.jpg?v=1446676415"}'+ ']'; asm var JS_Array = JSON.parse(BigSampleData); console.log('JS Array = '+JSON.stringify(JS_Array)); end; WC_Array := TJSArray(TJSJSON.parseObject(BigSampleData)); console.log('WC Array = '+TJSJSON.stringify(WC_Array)); PAS_Array := TJSONObject.ParseJSONValue(BigSampleData) as TJSONArray; console.log('PAS Array = '+PAS_Array.ToString); end; console.log output omitted for brevity.
04 : Accessing JSON Object Values.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Object: TJSObject; PAS_Object: TJSONObject; SampleObjectData: WideString; begin SampleObjectData := '{"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable"}'; asm var JS_Object = JSON.parse(SampleObjectData); end; WC_Object := TJSJSON.parseObject(SampleObjectData); PAS_Object := TJSONObject.ParseJSONValue(SampleObjectData) as TJSONObject; asm console.log('JS Carrot: '+JS_Object['carrot']); end; console.log('WC Carrot: '+string(WC_Object['carrot'])); console.log('PAS Carrot: '+(PAS_Object.getValue('carrot') as TJSONString).Value); end; console.log output: JS Carrot: vegetable WC Carrot: vegetable PAS Carrot: vegetable
05 : Accessing JSON Array Values.
Using JSON Arrays isn't much different than other Delphi arrays, just that the type doesn't need to be set when the array is defined, nor is it fixed for all the elements of the Array. Compared to accessing JSON Objects, the JavaScript code doesn't change much but the Delphi code has to jump through more hoops.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Array: TJSArray; PAS_Array: TJSONArray; const SampleArrayData = '[{"name":"apple","type":"fruit"},{"name":"banana","type":"fruit"},{"name":"orange","type":"fruit"},{"name":"carrot","type":"vegetable"},{"name":"potato","type":"vegetable"}]'; begin asm var JS_Array = JSON.parse(SampleArrayData); end; WC_Array := TJSArray(TJSJSON.parseObject(SampleArrayData)); PAS_Array := TJSONObject.ParseJSONValue(SampleArrayData) as TJSONArray; asm console.log('JS Array[3] is: '+JS_Array[3].name+' / '+JS_Array[3].type); end; console.log('WC Array[3] is: '+string(TJSObject(WC_Array[3])['name'])+' / '+string(TJSObject(WC_Array[3])['type'])); console.log('PAS Array[3] is: '+((PAS_Array[3] as TJSONObject).getValue('name') as TJSONString).Value+' / '+((PAS_Array[3] as TJSONObject).getValue('type') as TJSONString).Value); end; console.log output: JS Array[3] is: carrot / vegetable WC Array[3] is: carrot / vegetable PAS Array[3] is: carrot / vegetable
06 : Counting JSON Object Elements.
Sometimes you might want to know how many Objects are in your JSON. This is just the first level count, and doesn't traverse whatever nested Objects or Arrays you might have. We'll have a look at that a bit later. For the adventurous, JavaScript has a few other tricks up its sleeve when it comes to defining Keys as symbols in JavaScript. But we're going to look past those as they may not be all that generally applicable outside of JavaScript. Check out Object.getOwnPropertySymbols if you think that sort of thing might apply to you.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Object: TJSObject; PAS_Object: TJSONObject; 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 console.log('JS Object Elements: '+Object.keys(JS_Object).length); end; console.log('WC Object Elements: '+IntToStr(length(TJSObject.keys(WC_Object)))); console.log('PAS Object Elements: '+IntToStr(PAS_Object.count)); end; console.log output: JS Object Elements: 5 WC Object Elements: 5 PAS Object Elements: 5
07 : JSON Array Length.
This is a little easier as Arrays generally are, out of necessity, a little more self-aware.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Array: TJSArray; PAS_Array: TJSONArray; const SampleArrayData = '[{"name":"apple","type":"fruit"},{"name":"banana","type":"fruit"},{"name":"orange","type":"fruit"},{"name":"carrot","type":"vegetable"},{"name":"potato","type":"vegetable"}]'; begin asm var JS_Array = JSON.parse(SampleArrayData); end; WC_Array := TJSArray(TJSJSON.parseObject(SampleArrayData)); PAS_Array := TJSONObject.ParseJSONValue(SampleArrayData) as TJSONArray; asm console.log('JS Array Length: '+JS_Array.length); end; console.log('WC Array Length: '+IntToStr(WC_Array.length)); console.log('PAS Array Length: '+IntToStr(PAS_Array.Count)); end; console.log output: JS Array Length: 5 WC Array Length: 5 PAS Array Length: 5
08 : Checking JSON Object Types.
JavaScript will frequently be quite happy to gloss over any kinds of issues with data types. It makes various assumptions and just carries on, sometimes leaving you with gaps in your UI (or worse) where you'd expect data, if everything doesn't fall into place as expected. Delphi on the other hand doesn't take kindly to this kind of thing, so It's likely to stop right in the middle of whatever it is doing if you try to pass off one data type as another in a way that it isn't prepared for. Fortunately, the list of types we're likely to encounter is very short, so checking for them isn't too onerous. We only have to check for Strings, Numbers, True, False, Null, Array or Object. Here then is one way to do this for each of our three environments. It is a bit tedious of course, but it gets the job done.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Object: TJSObject; PAS_Object: TJSONObject; const SampleObjectData = '{"a":"some text","b":12,"c":3.14159,"d":["Delphi","JavaScript"],"e":true,"f":false,"g":null,"h":{"somekey":"somevalue"}}'; function WC_IdentifyType(Something: TJSObject; Key:String):String; var i: integer; begin Result := 'Unknown'; if (Something[key] = nil) then begin if string(Something[Key]) = 'null' then Result := 'Null' else Result := 'KEY NOT FOUND'; end else begin if (Something[Key] = True) then Result := 'True' else if (Something[Key] = False) then Result := 'False' else if (TJSJSON.stringify(Something[Key]).startsWith('"')) then Result := 'String' else if (TJSJSON.stringify(Something[Key]).startsWith('{')) then Result := 'Object' else if (TJSJSON.stringify(Something[Key]).startsWith('[')) then Result := 'Array' else Result := 'Number'; end; end; function PAS_IdentifyType(Something: TJSONObject; Key:String):String; var i: integer; begin Result := 'Unknown'; if not(Something.get(Key) is TJSONPair) then begin i := 0; Result := 'KEY NOT FOUND'; while (i < Something.Count) do begin if Something.Pairs[i].ToString.StartsWith('"'+Key+'":') then begin Result := 'Null'; break; end; i := i +1; end; end else begin Result := Copy(Something.get(Key).JSONValue.ClassName, 6, MaxInt); 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_IdentifyType(Something, Key) { var result = 'Unknown'; if (Something[Key] === undefined) {result = 'KEY NOT FOUND'} else if (Something[Key] === null) {result = 'Null'} else if (Something[Key] === true) {result = 'True'} else if (Something[Key] === false) {result = 'False'} else if (Array.isArray(Something[Key])) {result = 'Array'} else if (typeof Something[Key] === 'string') {result = 'String'} else if (typeof Something[Key] === 'number') {result = 'Number'} else if (typeof Something[Key] === 'object') {result = 'Object'} return(result); } console.log('JS a: '+JS_IdentifyType(JS_Object, 'a')); console.log('JS b: '+JS_IdentifyType(JS_Object, 'b')); console.log('JS c: '+JS_IdentifyType(JS_Object, 'c')); console.log('JS d: '+JS_IdentifyType(JS_Object, 'd')); console.log('JS e: '+JS_IdentifyType(JS_Object, 'e')); console.log('JS f: '+JS_IdentifyType(JS_Object, 'f')); console.log('JS g: '+JS_IdentifyType(JS_Object, 'g')); console.log('JS h: '+JS_IdentifyType(JS_Object, 'h')); console.log('JS X: '+JS_IdentifyType(JS_Object, 'X')); end; console.log('WC a: '+WC_IdentifyType(WC_Object, 'a')); console.log('WC b: '+WC_IdentifyType(WC_Object, 'b')); console.log('WC c: '+WC_IdentifyType(WC_Object, 'c')); console.log('WC d: '+WC_IdentifyType(WC_Object, 'd')); console.log('WC e: '+WC_IdentifyType(WC_Object, 'e')); console.log('WC f: '+WC_IdentifyType(WC_Object, 'f')); console.log('WC g: '+WC_IdentifyType(WC_Object, 'g')); console.log('WC h: '+WC_IdentifyType(WC_Object, 'h')); console.log('WC X: '+WC_IdentifyType(WC_Object, 'X')); console.log('PAS a: '+PAS_IdentifyType(PAS_Object, 'a')); console.log('PAS b: '+PAS_IdentifyType(PAS_Object, 'b')); console.log('PAS c: '+PAS_IdentifyType(PAS_Object, 'c')); console.log('PAS d: '+PAS_IdentifyType(PAS_Object, 'd')); console.log('PAS e: '+PAS_IdentifyType(PAS_Object, 'e')); console.log('PAS f: '+PAS_IdentifyType(PAS_Object, 'f')); console.log('PAS g: '+PAS_IdentifyType(PAS_Object, 'g')); console.log('PAS h: '+PAS_IdentifyType(PAS_Object, 'h')); console.log('PAS X: '+PAS_IdentifyType(PAS_Object, 'X')); end; console.log output: JS a: String JS b: Number JS c: Number JS d: Array JS e: True JS f: False JS g: Null JS h: Object JS X: KEY NOT FOUND WC a: String WC b: Number WC c: Number WC d: Array WC e: True WC f: False WC g: Null WC h: Object WC X: KEY NOT FOUND PAS a: String PAS b: Number PAS c: Number PAS d: Array PAS e: True PAS f: False PAS g: Null PAS h: Object PAS X: KEY NOT FOUND
09 : Check if JSON Object Contains a Key.
This can get more complicated when we first have to find the portion of JSON we're interested in, but for now we'll assume it is just the first level. Also note that the JSON standard doesn't say anything about duplicate entries in Objects or Arrays.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Object: TJSObject; PAS_Object: TJSONObject; 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 if (JS_Object['carrot']) {console.log('JS Object contains carrot')} else {console.log('JS Object does not contain carrot')} if (JS_Object['Batman']) { console.log('JS Object contains Batman')} else {console.log('JS Object does not contain Batman')} end; if WC_Object['carrot'] <> nil then console.log('WC Object contains carrot') else console.log('WC Object does not contain carrot'); if WC_Object['Batman'] <> nil then console.log('WC Object contains Batman') else console.log('WC Object does not contain Batman'); if PAS_Object.getValue('carrot') <> nil then console.log('PAS Object contains carrot') else console.log('PAS Object does not contain carrot'); if PAS_Object.getValue('Batman') <> nil then console.log('PAS Object contains Batman') else console.log('PAS Object does not contain Batman'); end; console.log output: JS Object contains carrot JS Object does not contain Batman WC Object contains carrot WC Object does not contain Batman PAS Object contains carrot PAS Object does not contain Batman
10 : Find String in JSON Array.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Array: TJSArray; PAS_Array: TJSONArray; const SampleArrayData = '["apple","banana","orange","carrot","potato"]'; function FindArrString(AJSONArray: TJSONArray; SearchString:String):Integer; var i: integer; begin Result := -1; i := 0; while i < AJSONArray.Count do begin if (AJSONArray[i].Value = SearchString) then begin Result := i; break; end; i := i + 1; end; end; begin asm var JS_Array = JSON.parse(SampleArrayData); end; WC_Array := TJSArray(TJSJSON.parseObject(SampleArrayData)); PAS_Array := TJSONObject.ParseJSONValue(SampleArrayData) as TJSONArray; asm console.log('JS Array: carrot position = '+JS_Array.indexOf('carrot')); console.log('JS Array: Batman position = '+JS_Array.indexOf('Batman')); end; console.log('WC Array: carrot position = '+IntToStr(WC_Array.indexOf('carrot'))); console.log('WC Array: Batman position = '+IntToStr(WC_Array.indexOf('Batman'))); console.log('PAS Array: carrot position = '+IntToStr(FindArrString(PAS_Array,'carrot'))); console.log('PAS Array: Batman position = '+IntToStr(FindArrString(PAS_Array,'Batman'))); end; console.log output: JS Array: carrot position = 3 JS Array: Batman position = -1 WC Array: carrot position = 3 WC Array: Batman position = -1 PAS Array: carrot position = 3 PAS Array: Batman position = -1
11 : Find Key in JSON Array.
It would be convenient if we only had Arrays of string values and nothing more complicated than that, but typically it will almost always be more complicated. The next step up is to find the Array index corresponding to a particular Key. We'll eventually get around to something even more generic and thus more useful a bit later. For now, searching for a Key means that we know that our Array is populated with Objects. JavaScript has a fancy way to do this that doesn't translate particularly well (or at least I have no idea how to write it!) for the WC implementation, so here the approaches for WC and PAS are more or less the same. Note also that an Array of Objects might have more than one Key:Value pair in each Array element, so we'll need to know which one we'll be using as our Key for the Array.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Array: TJSArray; PAS_Array: TJSONArray; const SampleArrayData = '[{"name":"apple","type":"fruit"},{"name":"banana","type":"fruit"},{"name":"orange","type":"fruit"},{"name":"carrot","type":"vegetable"},{"name":"potato","type":"vegetable"}]'; function WC_FindArrKey(AJSArray: TJSArray; SearchKeyName:String; SearchKey:String):Integer; var i: integer; begin Result := -1; i := 0; while i < AJSArray.Length do begin if (TJSObject(AJSArray[i])[SearchKeyName] = SearchKey) then begin Result := i; break; end; i := i + 1; end; end; function PAS_FindArrKey(AJSONArray: TJSONArray; SearchKeyName:String; SearchKey:String):Integer; var i: integer; begin Result := -1; i := 0; while i < AJSONArray.Count do begin if (((AJSONArray[i] as TJSONObject).getValue(SearchKeyName) as TJSONSTring).Value = SearchKey) then begin Result := i; break; end; i := i + 1; end; end; begin asm var JS_Array = JSON.parse(SampleArrayData); end; WC_Array := TJSArray(TJSJSON.parseObject(SampleArrayData)); PAS_Array := TJSONObject.ParseJSONValue(SampleArrayData) as TJSONArray; asm console.log('JS Array: carrot position = '+JS_Array.findIndex(obj => obj.name == 'carrot')); console.log('JS Array: Batman position = '+JS_Array.findIndex(obj => obj.name == 'Batman')); end; console.log('WC Array: carrot position = '+IntToStr(WC_FindArrKey(WC_Array,'name','carrot'))); console.log('WC Array: Batman position = '+IntToStr(WC_FindArrKey(WC_Array,'name','Batman'))); console.log('PAS Array: carrot position = '+IntToStr(PAS_FindArrKey(PAS_Array,'name','carrot'))); console.log('PAS Array: Batman position = '+IntToStr(PAS_FindArrKey(PAS_Array,'name','Batman'))); end; console.log output: JS Array: carrot position = 3 JS Array: Batman position = -1 WC Array: carrot position = 3 WC Array: Batman position = -1 PAS Array: carrot position = 3 PAS Array: Batman position = -1
12 : Adding Strings to a JSON Object.
Naturally, JSON Objects are not in any way required to be static. Adding a new element is one of the easier tasks on this list. Here, we're just adding another element similar to the others. Later we'll see how this can be extended to add more complex objects to other more complex objects.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Object: TJSObject; PAS_Object: TJSONObject; 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 JS_object['pineapple'] = 'fruit'; end; WC_Object['pineapple'] := 'fruit'; PAS_Object.AddPair('pineapple','fruit'); asm console.log('JS object = '+JSON.stringify(JS_object)); end; console.log('WC object = '+TJSJSON.stringify(WC_Object)); console.log('PAS object = '+PAS_Object.ToString); end; console.log output: JS object = {"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable","pineapple":"fruit"} WC object = {"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable","pineapple":"fruit"} PAS object = {"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"vegetable","potato":"vegetable","pineapple":"fruit"}
13 : Adding Strings to a JSON Array.
Here we're doing the same thing, but adding to an Array instead of an Object. As Arrays are ordered, there's also the option of where in the Array to add an element. The most common choices here would be to add to the end of the Array, to the beginning of the Array, or at a specific point in the Array. In the last case, we'll lookup the position of an existing element so we can add a new element just before it.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Array: TJSArray; PAS_Array: TJSONArray; i: integer; tmparray: TJSONArray; const SampleArrayData = '[{"name":"apple","type":"fruit"},{"name":"banana","type":"fruit"},{"name":"orange","type":"fruit"},{"name":"carrot","type":"vegetable"},{"name":"potato","type":"vegetable"}]'; begin asm var JS_Array = JSON.parse(SampleArrayData); end; WC_Array := TJSArray(TJSJSON.parseObject(SampleArrayData)); PAS_Array := TJSONObject.ParseJSONValue(SampleArrayData) as TJSONArray; // #1: Add pineapple to the end of the array // #2: add radish to the beginning of the array // #3: add peach before carrot in the array asm // #1: Add pineapple to the end of the array JS_Array.push({"name":"pineapple","type":"fruit"}); // #2: add radish to the beginning of the array JS_Array.unshift({"name":"radish","type":"vegetable"}); // #3: add peach before carrot in the array JS_Array.splice(JS_Array.findIndex(obj => obj.name == 'carrot'),0,{"name":"peach","type":"fruit"}); end; // #1: Add pineapple to the end of the array WC_Array.push(TJSJSON.parseObject('{"name":"pineapple","type":"fruit"}')); // #2: add radish to the beginning of the array WC_Array.unshift(TJSJSON.parseObject('{"name":"radish","type":"vegetable"}')); // #3: add peach before carrot in the array i := 0; while i < WC_Array.length do begin if string(TJSObject(WC_Array[i])['name']) = 'carrot' then begin WC_Array.splice(i,0,TJSJSON.parseObject('{"name":"peach","type":"fruit"}')); break; end; i := i + 1; end; // #1: Add pineapple to the end of the array PAS_Array.Add(TJSONObject.ParseJSONValue('{"name":"pineapple","type":"fruit"}') as TJSONObject); // #2: add radish to the beginning of the array tmparray := TJSONObject.ParseJSONValue('[{"name":"radish","type":"vegetable"}]') as TJSONArray; i := 0; while i < PAS_Array.count do begin tmparray.Add(Pas_Array[i] as TJSONObject); i := i + 1; end; PAS_Array := tmpArray; // #3: add peach before carrot in the array PAS_Array.Add(TJSONObject.ParseJSONValue('{"name":"pineapple","type":"fruit"}') as TJSONObject); tmparray := TJSONObject.ParseJSONValue('[]') as TJSONArray; i := 0; while i < PAS_Array.count do begin if (((Pas_Array[i] as TJSONObject).getValue('name') as TJSONString).Value = 'carrot') then tmpArray.Add(TJSONObject.ParseJSONValue('{"name":"peach","type":"fruit"}') as TJSONObject); tmparray.Add(Pas_Array[i] as TJSONObject); i := i + 1; end; PAS_Array := tmpArray; asm console.log('JS array = '+JSON.stringify(JS_Array)); end; console.log('WC array = '+TJSJSON.stringify(WC_Array)); console.log('PAS array = '+PAS_Array.ToString); end; console.log output: JS array = [{"name":"radish","type":"vegetable"},{"name":"apple","type":"fruit"},{"name":"banana","type":"fruit"},{"name":"orange","type":"fruit"},{"name":"peach","type":"fruit"},{"name":"carrot","type":"vegetable"},{"name":"potato","type":"vegetable"},{"name":"pineapple","type":"fruit"}] WC array = [{"name":"radish","type":"vegetable"},{"name":"apple","type":"fruit"},{"name":"banana","type":"fruit"},{"name":"orange","type":"fruit"},{"name":"peach","type":"fruit"},{"name":"carrot","type":"vegetable"},{"name":"potato","type":"vegetable"},{"name":"pineapple","type":"fruit"}] PAS array = [{"name":"radish","type":"vegetable"},{"name":"apple","type":"fruit"},{"name":"banana","type":"fruit"},{"name":"orange","type":"fruit"},{"name":"peach","type":"fruit"},{"name":"carrot","type":"vegetable"},{"name":"potato","type":"vegetable"},{"name":"pineapple","type":"fruit"}]
14 : Adding Other Types to a JSON Object.
While strings are likely to be the most common data type used when working with JSON, adding the other types is likely to come up from time to time. Note that for desktop applications (VCL And FireMonkey, for example) this is an area that needs a little extra attention. First, these functions should likely be included in a try...except block as they may attempt to allocate memory and fail to get it, or they may allocate memory and fail to release it afterwards. Not an issue for TMS WEB Core though :-)
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Object: TJSObject; PAS_Object: TJSONObject; begin asm var JS_Object = {}; JS_Object['a'] = 'some text'; JS_Object['b'] = 12; JS_Object['c'] = 3.14159; JS_Object['d'] = ["Delphi","JavaScript"]; JS_Object['e'] = true; JS_Object['f'] = false; JS_Object['g'] = null; JS_Object['h'] = {"somekey":"somevalue"}; end; WC_Object := TJSObject.new; WC_Object['a'] := 'some text'; WC_Object['b'] := 12; WC_Object['c'] := 3.14159; WC_Object['d'] := TJSArray(TJSJSON.parseObject('["Delphi","JavaScript"]')); WC_Object['e'] := true; WC_Object['f'] := false; WC_Object['g'] := null; WC_Object['h'] := TJSJSON.parseObject('{"somekey":"somevalue"}'); PAS_Object := TJSONObject.Create; PAS_Object.AddPair('a','some text'); PAS_Object.AddPair('b',TJSONNumber.Create(12)); PAS_Object.AddPair('c',TJSONNumber.Create(3.14159)); PAS_Object.AddPair('d',TJSONObject.ParseJSONValue('["Delphi","JavaScript"]') as TJSONArray); PAS_Object.AddPair('e',TJSONTrue.Create); PAS_Object.AddPair('f',TJSONFalse.Create); PAS_Object.AddPair('g',TJSONNull.Create); PAS_Object.AddPair('h',TJSONObject.ParseJSONValue('{"somekey":"somevalue"}') as TJSONObject); asm console.log('JS Object = '+JSON.stringify(JS_Object)); end; console.log('WC Object = '+TJSJSON.stringify(WC_Object)); console.log('PAS Object = '+PAS_Object.ToString); end; console.log output: JS Object = {"a":"some text","b":12,"c":3.14159,"d":["Delphi","JavaScript"],"e":true,"f":false,"g":null,"h":{"somekey":"somevalue"}} WC Object = {"a":"some text","b":12,"c":3.14159,"d":["Delphi","JavaScript"],"e":true,"f":false,"g":null,"h":{"somekey":"somevalue"}} PAS Object = {"a":"some text","b":12,"c":3.14159,"d":["Delphi","JavaScript"],"e":true,"f":false,"g":null,"h":{"somekey":"somevalue"}}
15 : Delete JSON Object Element.
Here, we're removing a Key/Value pair from an Object. For small JSON Objects, this is likely not a big deal, though it is likely costly relative to some of the other operations if done repeatedly in short loops, for example. In JavaScript this is done, oddly, using a delete statement instead of some kind of function tacked onto the Object itself. For WC and PAS, there doesn't appear to be an equivalent, so we do it the hard way. Note that this is also shows an example of one way to iterate through all of the Object elements as well.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Object: TJSObject; PAS_Object: TJSONObject; i: integer; tmp_wc_object: TJSObject; tmp_pas_object: TJSONObject; 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; // Deceptively simple, but why is it a statement and not a function? Oddly inconsistent. asm delete JS_Object['carrot']; end; // No delete option in Delphi i := 0; tmp_wc_object := TJSObject.new; while i < length(TJSObject.keys(WC_Object)) do begin if not(String(TJSObject.keys(WC_Object)[i]) = 'carrot') then tmp_wc_object[String(TJSObject.keys(WC_Object)[i])] := WC_Object[String(TJSObject.keys(WC_Object)[i])]; i := i + 1; end; WC_Object := tmp_wc_object; // Wherefore Art Thou TJSONObject.RemovePair? tmp_pas_object := TJSONObject.Create; i := 0; while i < PAS_Object.Count do begin if not(PAS_Object.Pairs[i].toString.startsWith('"carrot":')) then tmp_pas_object.AddPair(PAS_Object.Pairs[i]); i := i + 1; end; PAS_Object := tmp_pas_object; asm console.log('JS object = '+JSON.stringify(JS_Object)); end; console.log('WC object = '+TJSJSON.stringify(WC_Object)); console.log('PAS object = '+PAS_Object.ToString); end; console.log output: JS object = {"apple":"fruit","banana":"fruit","orange":"fruit","potato":"vegetable"} WC object = {"apple":"fruit","banana":"fruit","orange":"fruit","potato":"vegetable"} PAS object = {"apple":"fruit","banana":"fruit","orange":"fruit","potato":"vegetable"}
16 : Delete JSON Array String Element.
Deleting elements from Arrays is perhaps a little unusual given how Arrays are normally used, but what the heck, why not? It is generally not advisable as it is likely that the underlying implementation (well, the JavaScript implementation in this case) the memory consumed by the Array element being deleted may not actually be released but rather just left in the Array as a null value. In WC and PAS we're actually recreating the Array so this is less of an issue, though the overhead of doing that is arguably much worse. As previously, when deleting JSON Object elements, this is likely harmless on a small scale but if this is happening a lot in a short loop, you might very well want to consider another approach. Here we're just deleting the third element of the Array.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Array: TJSArray; PAS_Array: TJSONArray; i: integer; tmp_pas_Array: TJSONArray; const SampleArrayData = '["apple","banana","orange","carrot","potato"]'; begin asm var JS_Array = JSON.parse(SampleArrayData); end; WC_Array := TJSArray(TJSJSON.parseObject(SampleArrayData)); PAS_Array := TJSONObject.ParseJSONValue(SampleArrayData) as TJSONArray; asm JS_Array.splice(3,1); end; WC_Array.splice(3,1); // No TJSONArray.Remove() ??? i := 0; tmp_PAS_Array := TJSONArray.Create; while (i < PAS_Array.Count) do begin if i <> 3 then tmp_pas_Array.Add(PAS_Array[i].Value); i := i + 1; end; PAS_Array := tmp_pas_array; asm console.log('JS Array = '+JSON.stringify(JS_Array)); end; console.log('WC Array = '+TJSJSON.stringify(WC_Array)); console.log('PAS Array = '+PAS_Array.ToString); end; console.log output: JS Array = ["apple","banana","orange","potato"] WC Array = ["apple","banana","orange","potato"] PAS Array = ["apple","banana","orange","potato"]
17 : Delete JSON Array Key.
In this example, let's assume the Array element we want to delete has a particular Value in one of its Keys. This is similar to locating Keys in the Array, with just the extra step of deleting it once we find it. Or in the PAS case, recreating the Array but just not adding the matching element.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Array: TJSArray; PAS_Array: TJSONArray; i: integer; tmparray: TJSONArray; const SampleArrayData = '[{"name":"apple","type":"fruit"},{"name":"banana","type":"fruit"},{"name":"orange","type":"fruit"},{"name":"carrot","type":"vegetable"},{"name":"potato","type":"vegetable"}]'; begin asm var JS_Array = JSON.parse(SampleArrayData); end; WC_Array := TJSArray(TJSJSON.parseObject(SampleArrayData)); PAS_Array := TJSONObject.ParseJSONValue(SampleArrayData) as TJSONArray; asm JS_Array.splice(JS_Array.findIndex(obj => obj.name == 'carrot'),1); end; i := 0; while i < WC_Array.length do begin if string(TJSObject(WC_Array[i])['name']) = 'carrot' then begin WC_Array.splice(i,1); break; end; i := i + 1; end; tmparray := TJSONObject.ParseJSONValue('[]') as TJSONArray; i := 0; while i < PAS_Array.count do begin if not(((Pas_Array[i] as TJSONObject).getValue('name') as TJSONString).Value = 'carrot') then tmparray.Add(Pas_Array[i] as TJSONObject); i := i + 1; end; PAS_Array := tmpArray; asm console.log('JS array = '+JSON.stringify(JS_Array)); end; console.log('WC array = '+TJSJSON.stringify(WC_Array)); console.log('PAS array = '+PAS_Array.ToString); end; console.log output: JS array = [{"name":"apple","type":"fruit"},{"name":"banana","type":"fruit"},{"name":"orange","type":"fruit"},{"name":"potato","type":"vegetable"}] WC array = [{"name":"apple","type":"fruit"},{"name":"banana","type":"fruit"},{"name":"orange","type":"fruit"},{"name":"potato","type":"vegetable"}] PAS array = [{"name":"apple","type":"fruit"},{"name":"banana","type":"fruit"},{"name":"orange","type":"fruit"},{"name":"potato","type":"vegetable"}]
18 : Update JSON Object Element.
procedure TForm1.WebButton1Click(Sender: TObject);
var
WC_Object: TJSObject;
PAS_Object: TJSONObject;
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 JS_Object['carrot'] = 'not fruit'; end;
WC_Object['carrot'] := 'not fruit';
PAS_Object.AddPair('carrot','not fruit');
asm console.log('JS object = '+JSON.stringify(JS_Object)); end;
console.log('WC object = '+TJSJSON.stringify(WC_Object));
console.log('PAS object = '+PAS_Object.ToString);
end;
console.log output:
JS object = {"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"not fruit","potato":"vegetable"}
WC object = {"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"not fruit","potato":"vegetable"}
PAS object = {"apple":"fruit","banana":"fruit","orange":"fruit","carrot":"not fruit","potato":"vegetable"}
19 : Duplicate JSON Object Keys.
Not the first surprise I ran across when preparing this material, but a curious one all the same. There's nothing in the JSON standards that I've run across that make any references to duplicate Keys. The term 'Key' might imply that it is unique, but that's not even the term used in the standards. And in fact if you pass JSON with duplicate keys to a formatting or validation tool, it will format and validate it all the same and strip out the duplicate Keys. Importantly, the last Value is used in the case of duplicate Keys. This is surprising to me as "the last Value" really shouldn't have a meaning in set theory but yet here we are. If you treat Key names as actual keys, then this is naturally a non-issue. The fact that it is treated in this way suggests that in fact this should be clarified as one of the rules specified at the outset.
procedure TForm1.WebButton1Click(Sender: TObject); var WC_Object: TJSObject; PAS_Object: TJSONObject; const SampleObjectData = '{"some key":"value 1", "some key":"value 2", "some key":"value 3", "some key":"value 4", "some key":"value 5"}'; begin asm var JS_Object = JSON.parse(SampleObjectData); end; WC_Object := TJSJSON.parseObject(SampleObjectData); PAS_Object := TJSONObject.ParseJSONValue(SampleObjectData) as TJSONObject; asm console.log('JS Object = '+JSON.stringify(JS_Object)); end; console.log('WC Object = '+TJSJSON.stringify(WC_Object)); console.log('PAS Object = '+PAS_Object.ToString); end; console.log output: JS object = {"some key":"value 5"} WC object = {"some key":"value 5"} PAS object = {"some key":"value 5"}
Part 2 coming soon...
Andrew Simard
Bookmarks:

This blog post has received 3 comments.

function WC_FindArrKey(pmJSArray: TJSArray; pmSearchKeyName: String; pmSearchKey: String): Integer;
begin
Result := pmJSArray.FindIndex(
function(element: JSValue; index: NativeInt; anArray: TJSArray): Boolean
begin
Result := (TJSObject(element)[pmSearchKeyName] = pmSearchKey);
// or: Result := (TJSObject(anArray[index])[pmSearchKeyName] = pmSearchKey);
end);
end;
tbo

Simard Andrew
All Blog Posts | Next Post | Previous Post
Borbor Mehmet Emin