Search code examples
jsondelphidelphi-10.2-tokyo

Adding a filter expression to a JSONIterator Find function call


What is the correct syntax for a filter expression to query a JSON object with a specific attribute string value inside an array, using bracket notation?

I am limited to bracket notation because dot notation won't work in Delphi when there is a quote or apostrophe inside the filter expression.

Use [] to access object properties that do contain a quoting character in their name. For example, use root['child.name'] or root["child.name"] to access the child.name property of the root object.

I've used an online JSON path evaluator against this JSON and come up with the expression result["elements"][?(@.name == 'Training Seminar - Nov 9')]. In the online evaluator, this path works fine and returns the exact object I'm looking for. However, when I run it in Delphi I get an exception that says

EJSONPathException: Invalid index for array: ?(@.name == 'Training Seminar - Nov 9')

My question is, what is the correct syntax for a filter expression to query a JSON object with a specific attribute string value inside an array, using bracket notation?

MCVE for this as a console application, including the JSON.

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, System.Classes, System.JSON.Builders, System.JSON.Readers, System.JSON.Types;

const JsonStr = '{' +
'   "result":{     ' +
'      "elements":[   ' +
'         {              ' +
'            "id":"ML_1HMloeUjEFgKaC9",' +
'            "name":"Utilization Survey",' +
'         },' +
'         {' +
'            "id":"ML_1zQjGtGXFPkEo6N",' +
'            "name":"Training Seminar - Nov 9",' +
'         }' +
'      ]' +
'   },' +
'   "meta":{' +
'      "httpStatus":"200 - OK",' +
'      "requestId":"ef2afd6e-3fd9-4fdf-a8fe-c935c147a0af"' +
'   }' +
'}';

procedure RunIt;
var Reader : TJsonTextReader;
  Iterator : TJsonIterator;
  StringReader : TStringReader;
  Found : Boolean;
begin
  StringReader := TStringReader.Create(JsonStr);
  Reader := TJsonTextReader.Create(StringReader);
  Iterator := TJsonIterator.Create(Reader);
  try
    Found := Iterator.Find('result["elements"][?(@.name == ''Training Seminar - Nov 9'')]');
    //The value of Found is false or an exception is raised because of bad syntax in the filter expression
    WriteLn(BoolToStr(Found));
    ReadLn;
  finally
    Iterator.Free;
    Reader.Free;
    StringReader.Free;
  end;
end;

begin
  try
    RunIt;
  except
    on E: Exception do
    begin
      Writeln(E.ClassName, ': ', E.Message);
      Readln;
    end
  end;
end.

Solution

  • You can't use expressions at all, because they are not implemented. Whether in dot notation or bracket notation. Excerpt from official documentation of System.JSON.TJSONPathParser.

    These operators do not support special expressions, they only support actual values (object properties or array indexes).

    However in this case, it can be achieved in a slightly more complicated way.

    procedure RunIt;
    var
      lJSON, lValue, lName: TJSONValue;
      lFound : Boolean;
    begin
      lFound := False;
      lJSON := TJSONObject.ParseJSONValue(JsonStr);
      if Assigned(lJSON) then
      try
        if lJSON.TryGetValue('result.elements', lValue) and (lValue is TJSONArray) then
        begin
          for lValue in TJSONArray(lValue) do
          begin
            lFound := lValue.TryGetValue('name', lName) and (lName.Value = 'Training Seminar - Nov 9');
            if lFound then
              Break;
          end;
        end;
      finally
        lJSON.Free;
      end;
      WriteLn(BoolToStr(lFound));
      ReadLn;
    end;