Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi parse JSON array or array

Tags:

json

delphi

This is the sample JSON I want to be able to parse:

[
  {
    "a":{
      "username":"aaa",
      "email":"[email protected]"
    }
  },
  {
    "b":{
      "username":"bbb",
      "email":"[email protected]"
    }
  }
]

I need that a call to getData('b', 'email') must output [email protected]!


I am really struggling to understand how to use the System.JSON unit, but I can't get the solution! I want to be able to write a function that extracts a particular data from the above kind of JSON structure. This is my code so far. In the class constructor I have:

var
  FIds: TJSONArray;
begin
  FIds := TJSONObject.ParseJSONValue({json string here}) as TJSONArray;
end;

Then, inside the function that must return the data, I have written this:

// 'name' can be 'a' or 'b'  | 'data' can be 'username' or 'email'
function TTest.getData(const name, data: string): string;
var
  FValue, FValueInner: TJSONValue;
begin
  for FValue in Fids do
  begin
    if (FValue is TJSONArray) then
    begin
      //Here I am sure that I have a TJSONArray (which can be 'a' or 'b' from above)
    end;
  end;
end;

According to what I have written above, I have to check the value of name and decide if I have to access the data inside a or b. Then, once I have picked the correct JSON array a or b, I have to select if I want to display the username or the email field (which is specified inside the data variable).

How can I do this?


This is my latest attempt but I really can't understand what to do:

... same code above ...

if (FValue is TJSONArray) then
begin
  //here I want to check if the current array is a or b
  if ((FValue as TJSONArray).Items[0] as TJSONValue).Value = name then
  begin
    Farr := TJSONObject.ParseJSONValue(((FValue as TJSONArray).Items[0] as TJSONValue).ToJSON) as TJSONArray;
    try
      //here I want to get the data inside username or email
      for FValueInner in Farr do
        Result := FValueInner.GetValue<string>(data);
    finally
      Farr.Free;
    end;
  end;
end;

Where Farr: TJSONArray; and FValueInner: TJSONValue;

like image 258
Raffaele Rossi Avatar asked Oct 02 '17 21:10

Raffaele Rossi


2 Answers

For new readers looking for these answers.

How about this function, or even simpler if you restructure the JSON data?

function getData(JsonString: String; User: String; Field: String): String;
var
  JSonValue: TJSonValue;
  JsonArray: TJSONArray;
  ArrayElement: TJSonValue;
  FoundValue: TJSonValue;
begin
  Result :='';

  // create TJSonObject from string
  JsonValue := TJSonObject.ParseJSONValue(JsonString);

  // get the array
  JsonArray := JsonValue as TJSONArray;

  // iterate the array
  for ArrayElement in JsonArray do begin
      FoundValue := ArrayElement.FindValue(User);
      if FoundValue <> nil then begin
        Result := ArrayElement.GetValue<string>(User + '.' + Field);
        break;
      end;
  end;
end;

The problem with the sample JSON code above is that it uses the users' names "a" "b" as a JSON-key {key:data} for the users' data. In this way you can't use GetValue("a") in your search for data. Structuring your JSON data differently makes the search process a lot easier. I will later on give an example of this.

A way to handle the given JSON data is by using FindValue so you can check if a field with key "a" or "b" exists.

FoundValue := ArrayElement.FindValue("b");
if FoundValue <> nil then begin
    Result := ArrayElement.GetValue<string>("b"+ '.' + "email");
    break;

About the 'parsing a JSON array' question: After the data is loaded as a TJSonObject you can change the data into a TJSONArray and iterate the elements.

  JsonValue := TJSonObject.ParseJSONValue(JsonString);  
  JsonArray := JsonValue as TJSONArray;
  for ArrayElement in JsonArray do begin
    ...

A working example code for the given JSON data:

unit JsonArray1;

interface

uses System.JSON;

function getData2(JsonString: String; User: String; Field: String): String;
procedure Test1();

implementation

function getData2(JsonString: String; User: String; Field: String): String;
var
  JSonValue: TJSonValue;
  JsonArray: TJSONArray;
  ArrayElement: TJSonValue;
  FoundValue: TJSonValue;
begin
  Result :='';

  // create TJSonObject from string
  JsonValue := TJSonObject.ParseJSONValue(JsonString);

  // get the array
  JsonArray := JsonValue as TJSONArray;

  // iterate the array
  for ArrayElement in JsonArray do begin
      FoundValue := ArrayElement.FindValue(User);
      if FoundValue <> nil then begin
        Result := ArrayElement.GetValue<string>(User + '.' + Field);
        break;
      end;
  end;
end;

procedure Test1();
var
  DataBase: String;
  EmailAddress : String;
  Username: String;
begin
  DataBase := '[  {"a" : {"username":"aaa","email":"[email protected]"}},' +
                 '{"b" : {"username":"bbb","email":"[email protected]"}}  ]';

  EmailAddress := getData2(DataBase, 'b', 'email');
  Username := getData2(DataBase, 'a', 'username');

end;

end.

As already mentioned, restructuring the JSON data with proper keys makes the code to find data more simple. Because there is a 1 on 1 relation between the users' data "a":{}, "b":{} it's easy to introduce a 'user' key. Also adding a 'users' key to the array results in all data having keys.

  '{"users" : [{ "user":"a", "username":"aaa","email":"[email protected]"},' +
              '{ "user":"b", "username":"bbb","email":"[email protected]"}]}';

When you iterate the users, you can now use GetValue with the new "user" key.

  if ArrayElement.GetValue<String>('user') = 'b' then begin
    Result := ArrayElement.GetValue<String>('email');

By giving the array a key you can now get the array with:

JsonArray := JsonValue.GetValue<TJSONArray>('users');

A working example code for the restructured JSON data:

unit JsonArray2;

interface

uses System.JSON;

function getData2(JsonString: String; User: String; Field: String): String;
procedure Test2();

implementation

function getData2(JsonString: String; User: String; Field: String): String;
var
  JSonValue: TJSonValue;
  JsonArray: TJSONArray;
  ArrayElement: TJSonValue;
  FoundValue: TJSonValue;
begin
  Result :='';

  // create TJSonObject from string
  JsonValue := TJSonObject.ParseJSONValue(JsonString);

  // get the array
  JsonArray := JsonValue.GetValue<TJSONArray>('users');

  // iterate the array
  for ArrayElement in JsonArray do begin
      if ArrayElement.GetValue<String>('user') = User then begin
        Result := ArrayElement.GetValue<String>(Field);
        break;
      end;
  end;
end;

procedure Test2();
var
  DataBase: String;
  EmailAddress : String;
  Username: String;
begin
  DataBase := '{"users" : [{ "user":"a", "username":"aaa","email":"[email protected]"},' +
                          '{ "user":"b", "username":"bbb","email":"[email protected]"}]}';

  EmailAddress := getData2(DataBase, 'b', 'email');
  Username := getData2(DataBase, 'a', 'username');

end;

end.
like image 58
Mace Avatar answered Oct 31 '22 22:10

Mace


Your JSON is an array of objects, so FIds is a TJSONArray containing TJSONObject elements. And the a and b fields of those objects are themselves objects, not arrays. So FValue is TJSONArray will always be false while enumerating that array.

Also, (FValue as TJSONArray).Items[0] as TJSONValue).Value = name is wrong, because a JSON object contains name/value pairs, but you are ignoring the names, and you are trying to enumerate the pairs as if they are elements of an array when they are really not. If you want to enumerate an object's pairs, use the TJSONObject.Count and TJSONObject.Pairs[] property. But that is not necessary in this situation since you are looking for a specific pair by its name. TJSONObject has a Values[] property for that very purpose.

And TJSONObject.ParseJSONValue(((FValue as TJSONArray).Items[0] as TJSONValue).ToJSON) as TJSONArray is just plain ridiculous. There is no reason to convert an object back to a JSON string just to parse it again. It has already been parsed once, you don't need to parse it again.

And lastly, FValueInner.GetValue<string>(data) is wrong, because TJSONValue does not have a GetValue() method, let alone one that uses Generics.

Now, with that said, try something more like this instead:

// 'name' can be 'a' or 'b'  | 'data' can be 'username' or 'email'
function TTest.getData(const name, data: string): string;
var
  FValue, FValueInner: TJSONValue;
begin
  Result := '';
  for FValue in Fids do
  begin
    if (FValue is TJSONObject) then
    begin
      FValueInner := TJSONObject(FValue).Values[name];
      if FValueInner <> nil then
      begin
        if (FValueInner is TJSONObject) then
        begin
          FValueInner := TJSONObject(FValueInner).Values[data]; 
          if FValueInner <> nil then
            Result := FValueInner.Value;
        end;
        Exit;
      end;
    end;
  end;
end;
like image 7
Remy Lebeau Avatar answered Nov 01 '22 00:11

Remy Lebeau