1

I m parsing and go threw a JSON structure, in Delphi XE8, and it gives me an error because of the JSON Structure. if i use the REST Debugger i can see the data in the grid but if i try to get it manually i get an error.

here is the JSON :

{
  "S":1,
  "U":"2898",
  "F":[],
  "D":[
    {
      "PRJCT_ID":"7",
      "PRJCT_NAME":"Hotel La Rosiere",
      "PRJCT_ADRESS":"le plateau"
    },  
    {
      "PRJCT_ID":"8",
      "PRJCT_NAME":"Hotel Delux",
      "PRJCT_ADRESS":"Centre Maison"
    },
    {
      "PRJCT_ID":"9",
      "PRJCT_NAME":"villedieu",
      "PRJCT_ADRESS":""
    }
  ]
}

I can get the Pair list from level 1 properly : S, F, D

But how can i test if the data is an anonymous record of data : i tried :

 if JSO.Pairs[i].JsonString.Value = '' then

I get an Exception EListError Message... which is normal because no JSON Key associated for the value "{"PRJCT_ID":"7","PRJCT_NAME":"Hotel La Rosiere","PRJCT_ADRESS":"le plateau"}"

My code is

procedure TAArray2.ExtractValues(JSO : TJsonObject);
var
  i : integer;
begin
  try
    i := 0;
    while (i < JSO.Count) do
    begin
      if JSO.Pairs[i].JsonValue is TJSONArray then   // array of values "Key":[{"Key":"Value","Key":"Value"},{"Key":"Value","Key":"Value"}]
      begin
        AddItem(JSO.Pairs[i].JsonString.Value, '', TJSonObject(JSO.Pairs[i].JsonValue)); // recursive call ExtractValues(TJSonObject(JSO.Pairs[i].JsonValue))
      end
      else if JSO.Pairs[i].JsonString.Value = '' then  // ERROR HERE : anonymous key : {"Key":"Value","Key":"Value"},{"Key":"Value","Key":"Value"} 
      begin
        AddItem(i, JSO.Pairs[i].JsonValue.Value);
      end
      else // standard creation : "Key":"Value"
      begin
        AddItem(JSO.Pairs[i].JsonString.Value, JSO.Pairs[i].JsonValue.Value);
      end;
      inc(i);
    end;
  finally

  end;
end;

How can i do it ? Does anyone get an idea ?

Nota : i call anonymous JSON Record Set the portion of JSON that don't is member of Array "D" like we could say : "D[1]":{ "PRJCT_ID":"7", "PRJCT_NAME":"Hotel La Rosiere", "PRJCT_ADRESS":"le plateau"} i call it anonymouse because this record set doesn't have his own Reference Key.

Here is the full code : about building a dictionnary of TREE - data accessing as well as Key string value or index : (Note i will only "Parse" the node if needed (Get), if not it will stay stored like a string)

unit Unit3;

interface

 uses
  Classes, System.SysUtils, System.Types, REST.Types, System.JSON, Data.Bind.Components,
  System.RegularExpressions, System.Variants,
  Generics.Collections;

type

  TAArray2 = class;

  PTRec=^TRec;

  TRec = class
  public
    Key : Variant;
    isRequired : boolean;
    Value : Variant;
    OldValue : Variant;
    JSON : string;
    JSO : TJSonObject;
    Items : TAArray2;
    procedure Add(Key : Variant ; Value: TRec);
  end;

   TAArray2 = class(TDictionary<Variant, TRec>)
   private
     function Get(Index: variant): TRec;
    procedure ExtractValues(JSO : TJsonObject);
   public
    procedure AddItem(Key: Variant; Value: Variant ; JSOnObject : TJSOnObject = nil);
    procedure ExtractFromJSON(JSonString: string ; RootElement : string = '');
     property Items[Cle : Variant]: TRec read Get; default;
   end;

implementation

procedure TRec.Add(Key : Variant ; Value: TRec);
begin
  if not(assigned(items)) then
    self.Items := TAArray2.Create;
  Items.Add( Key, Value);

end;

procedure TAArray2.AddItem(Key : Variant ; Value: Variant ; JSOnObject : TJSOnObject = nil);
var
  LocalRec : TRec;
begin

  LocalRec := Get(Key);

  if assigned(LocalRec) then
  begin

    LocalRec.Key := Key;
    LocalRec.Value := Value;
    LocalRec.JSO := JSOnObject;
  end
  else
  begin

   LocalRec := TRec.Create;
    LocalRec.Value := Value;
    LocalRec.Key := Key;
    LocalRec.JSO := JSOnObject;
    inherited Add( Key, LocalRec);

  end;

end;


function TAArray2.Get(Index: Variant): TRec;
var
  LocalRec : TRec;
begin

  if self.ContainsKey(Index) then
  begin
    LocalRec := inherited items[Index];
    if (LocalRec.JSON <> '') or (LocalRec.JSO <> nil) then
    begin
      LocalRec.Items := TAArray2.Create;

//      ExtractValues(JSO);
    end;

    Result := LocalRec;
  end
  else
  begin
    result := nil;

  end;

end;



// *****************************************************************************
//
// *****************************************************************************

procedure TAArray2.ExtractFromJSON(JSonString: string ; RootElement : string = '');
var
  JSO : TJsonObject;
  JSP : TJSonPair;
begin

  try
    JSO := TJSOnObject.ParseJSONValue(JSonString) as TJSONObject;

    try
      if (RootElement <> '') then
      begin
        JSP := JSO.Get(RootElement);
        if not(JSP.Null) then
        begin
          ExtractValues(TJSonObject(JSP.JsonValue));
        end;
      end
      else if Not(JSO.Null) then
      begin
        ExtractValues(JSO);
      end;

    finally
       JSO.Free();
    end;

  except
    on E:Exception do showmessage('Data Structure Error');
  end;
end;

I view the content with this code :

procedure TForm1.ShowAssocArray2(AAA : TAArray2 ; Level : integer);
var
  i : Integer;
  s : string;
  MyRec : TRec;
begin
  s := DupeString(' ',Level * 4);
  for MyRec in AAA.Values Do
  begin
    memo2.Lines.Add(s + string(MyRec.Key) + ' = ' + string(MyRec.Value) + ' (' + string(MyRec.JSON) + ')');  // Error Here
    AAA[MyRec.Key];
    if assigned(MyRec.Items) then
    begin
      if MyRec.Items.Count > 0 then
        ShowAssocArray2(MyRec.items, Level + 1);   // recursive for childrens
    end;
  end;
end;

procedure TForm1.Button5Click(Sender: TObject);
var
  MyList: TAArray2;
  MyRec, MyRec2 : TRec;
  i: Integer;
begin
  MyList := TAArray2.Create;
  MyList.ExtractFromJSON(Memo1.Lines.Text);

  ShowAssocArray2(MyList, 0);

end;
4
  • JSON is not valid, comma is missing after "2898". Have you tried to search? You should probably not use JsonString if you don't know the format. Eg. this stackoverflow.com/questions/10808912/… Commented May 1, 2015 at 15:18
  • Now the JSON is valid (i cut it manualy for easier understanding). I read the article, and i use the same technic. But the difference is my JSON get anonymous value (without reference key : JSonString). And the Pair is assigned (without JSonString reference) Commented May 1, 2015 at 15:24
  • 1
    There is no such thing as an "anonymous" value in JSON. Everything has a name, and everything in your JSON has a name. Commented May 1, 2015 at 16:17
  • Did you check my JSON ? the Key D have an array of 3 records that contain each 3 fields... Each record is anonymous because there is not a name like "record1":"{ "PRJCT_ID":"7", "PRJCT_NAME":"Hotel La Rosiere", "PRJCT_ADRESS":"le plateau"} . There is no name for this but i need to extract this record as it is and put a number. The JSON is correct because the REST debugger in delphi can read it properly. And i use this way of data since more than 5 years, it never cause problem in PHP, or Ajax, Or even if i use DataSetRestAdapter... Commented May 1, 2015 at 17:39

1 Answer 1

1

You are not accessing the array correctly. When you find a TJSONArray, you are type-casting it to TJSONObject, but an array is not an object. You would have gotten a runtime error had you used the as operator for the type-cast, but you did not.

You need to do something more like this instead:

procedure TAArray2.ExtractValues(JSO : TJSONObject);
var
  i, j: integer;
  pair: TJSONPair;
  arr: TJSONArray;
  value: TJSONvalue;
begin
  for i := 0 to JSO.Count-1 do
  begin
    pair := JSO.Pairs[i];
    value := pair.JsonValue;
    if value is TJSONArray then
    begin
      arr := TJSONArray(value);
      for j := 0 to arr.Count-1 do
      begin
        value := arr[j];
        if value is TJSONObject then
        begin
          ExtractValues(TJSONObject(value));
        end
        else
        begin
          // value is not an object, do something else...
        end;
      end;
    end
    else
    begin
      AddItem(pair.JsonString.Value, value.Value);
    end;
  end;
end;

Update: the JSON document is already in a tree structure. If you are trying to display that tree to the user, such as in a TTreeView, then you can use something like this:

function TAArray2.AddJSONValueToTreeView(const Name: String; Value: TJSONValue; ParentNode: TTreeNode = nil): TTreeNode;
var
  i: integer;
  obj: TJSONObject;
  pair: TJSONPair;
  arr: TJSONArray;
begin
  if ParentNode <> nil then
    Result := TreeView1.Items.AddChild(ParentNode, Name);
  else
    Result := TreeView1.Items.Add(nil, Name);
  if Value is TJSONObject then
  begin
    obj := TJSONObject(Value);
    for i := 0 to obj.Count-1 do
    begin
      pair := obj.Pairs[i];
      AddJSONValueToTreeView(pair.JsonString.Value, pair.JsonValue, Result);
    end;
  end
  else if Value is TJSONArray then
  begin
    arr := TJSONArray(Value);
    for i := 0 to arr.Count-1 do
    begin
      AddJSONValueToTreeView('['+IntToStr(i)+']', arr.Items[i], Result);
    end;
  end
  else
  begin
    TreeView1.Items.AddChild(Result, Value.Value);
  end;
end;
Sign up to request clarification or add additional context in comments.

9 Comments

U extracting all the data from the same level. This can't work because at the end : PRJCT_ID = 9 which is not right because there is 3 records with PRJCT_ID = 7 then 8 then 9... that's why i ask. I need to make a difference from the anonymous JSON RecordSet. (i did that before already. and it's not my need).
about what u said "You are not accessing the array correctly. When you find a TJSONArray, you are type-casting it to TJSONObject, but an array is not an object."... IT WORK PROPERLY i don't have any runtime error and i can get all the values the same way you do it, if i don't care about the anonymous Record. NO TYPECAST Error.
The problem is i need to really create the same kind of Tree structure that the data give : D : 1 : PRJCT_ID:"7" PRJCT_NAME:"Hotel La Rosiere" PRJCT_ADRESS:"le plateau" 2 : { fields } PRJCT_ID:"8" PRJCT_NAME:"Hotel Delux" PRJCT_ADRESS:"Centre Maison" 3 : { fields } PRJCT_ID:"9" PRJCT_NAME:"villedieu" PRJCT_ADRESS:"" Where 1, 2, 3 is a level / branch than contain every record set
@FabienFert: Your original code said "recursive call ExtractValues(TJSonObject(JSO.Pairs[i].JsonValue))", so I made ExtractValues() actually recurse itself, as you did not show what your AddItem() methods are actually doing. But you can clearly see that my code is recursing through arrays.
@FabienFert: You CANNOT type-cast a TJSONArray to a TJSONObject. They do not derive from each other, they both derive from TJSONValue instead. You did not get a runtime error on the type-cast because you were not performing a type-cast that performs a runtime validation to begin with. But you did get a runtime error when trying to access TJSONObject fields on a TJSONArray object.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.