Wednesday, 14 February 2018

JSON Find / Rewind

- or using the TJSONIterator without wearing a Cardigan.

Working with JSON files has been made easier over the years with Delphi - which btw turns 23 today - by either third-party libraries or especially within the RTL.

The System.JSON and the REST.JSON units have added the one-lines ObjectToJSON and JSONToObject, which can add a tiny bit of fat to the JSON generated to enable to get it back into your TObjectLists - but makes your code cleaner to read.

Another way is the adding of the TJSONWriter and TJSONReader which by using the TTextWriter and TTextReader mimics the .Net equivalents.

The TJSONWriter includes easy formatting of the output and in the System.JSON.Builders unit there are a couple of extra goodies, two of these being the TJSONObjectBuilder and the TJSONIterator.

I will dig a bit into TJSONIterator, since only its Recurse, Next and Return methods is mentioned on its DocWiki page here.

So if we basically take the code sample from the wiki page - but we only want to get the 2nd elements lastName and price values - iterating through the JSON using Recurse and Next might be a bit hard to follow. Se the wiki page as an example of their use.

We need: System.JSON.Builders, System.JSON.Readers

I create the TStringReader and the TJSONTextReader:

LStringReader := TStringReader.Create('{"Transaction":[' + sLineBreak
    + '{"id":662713, "firstName":"John", "lastName":"Doe", "price": 2.1},' + sLineBreak
    + '{"id":662714, "firstName":"Anna", "lastName":"Smith", "price": 4.5},' + sLineBreak
    + '{"id":662715, "firstName":"Peter", "lastName":"Jones", "price": 3.6} ' + sLineBreak
    + ']}');

So since I want to be able to use the Find method, the TJSONIterator needs to be create with a reference to a "Rewind" procedure.

  LJsonTextReader := TJsonTextReader.Create(LStringReader);
 LIterator := TJSONIterator.Create(LJsonTextReader,
    procedure (AReader: TJsonReader) begin end);

Note that I left the anonymous procedure empty since Rewind already calls rewind on its internal TJSONReader, but I could have added extra functionality. The iterator iterates through the JSON one-way, so its Find (and Rewind) needs to be able to restart from the beginning.

// Check if there is even the expected 3 elements in the array of interest
  if LIterator.Find('Transaction[2]') then
    if LIterator.Find('Transaction[1].lastName') then
      Memo1.Lines.Add(LIterator.AsString);  // Smith
      Memo1.Lines.Add(LIterator.AsDouble.ToString);  // 4.5

Just to illustrate the usefulness of Find (and Rewind), someone decided that in the JSON supplied, price was more important, so the order of the pairs in the array elements was changed to:

LStringReader := TStringReader.Create('{"Transaction":[' + sLineBreak
    + '{"id":662713, "price": 2.1, "firstName":"John", "lastName":"Doe"},' + sLineBreak
    + '{"id":662714, "price": 4.5, "firstName":"Anna", "lastName":"Smith"},' + sLineBreak
    + '{"id":662715, "price": 3.6, "firstName":"Peter", "lastName":"Jones"} ' + sLineBreak
    + ']}');

Now we would still get "Smith", but our price would return "0" - since I just used iterated with the Next('price') within the element and lastName now came after.

So if you need to parse the full JSON - Recurse, Next and Return it is, but if you need to pick out specific pairs, elements or just what to figure out if they exists in the provided JSON, TJSONIterator.Find can be helpful and keep your code more readable.

Did anyone get the Cardigan reference?

Enjoy, happy Valentine and Happy Birthday Delphi. :)